View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.discovery.tools;
18  
19  import java.util.HashMap;
20  import java.util.Properties;
21  
22  import org.apache.commons.discovery.DiscoveryException;
23  import org.apache.commons.discovery.jdk.JDKHooks;
24  import org.apache.commons.discovery.resource.ClassLoaders;
25  
26  
27  /***
28   * <p>Discover singleton service providers.
29   * This 
30   * </p>
31   * 
32   * <p>DiscoverSingleton instances are cached by the Discovery service,
33   * keyed by a combination of
34   * <ul>
35   *   <li>thread context class loader,</li>
36   *   <li>groupContext, and</li>
37   *   <li>SPI.</li>
38   * </ul>
39   * This DOES allow multiple instances of a given <i>singleton</i> class
40   * to exist for different class loaders and different group contexts.
41   * </p>
42   * 
43   * <p>In the context of this package, a service interface is defined by a
44   * Service Provider Interface (SPI).  The SPI is expressed as a Java interface,
45   * abstract class, or (base) class that defines an expected programming
46   * interface.
47   * </p>
48   * 
49   * <p>DiscoverSingleton provides the <code>find</code> methods for locating and
50   * instantiating a singleton instance of an implementation of a service (SPI).
51   * Each form of <code>find</code> varies slightly, but they all perform the
52   * same basic function.
53   * 
54   * The simplest <code>find</code> methods are intended for direct use by
55   * components looking for a service.  If you are not sure which finder(s)
56   * to use, you can narrow your search to one of these:
57   * <ul>
58   * <li>static Object find(Class spi);</li>
59   * <li>static Object find(Class spi, Properties properties);</li>
60   * <li>static Object find(Class spi, String defaultImpl);</li>
61   * <li>static Object find(Class spi,
62   *                        Properties properties, String defaultImpl);</li>
63   * <li>static Object find(Class spi,
64   *                        String propertiesFileName, String defaultImpl);</li>
65   * <li>static Object find(String groupContext, Class spi,
66   *                        Properties properties, String defaultImpl);</li>
67   * <li>static Object find(String groupContext, Class spi,
68   *                        String propertiesFileName, String defaultImpl);</li>
69   * </ul>
70   * 
71   * The <code>DiscoverSingleton.find</code> methods proceed as follows:
72   * </p>
73   * <ul>
74   *   <p><li>
75   *   Examine an internal cache to determine if the desired service was
76   *   previously identified and instantiated.  If found in cache, return it.
77   *   </li></p>
78   *   <p><li>
79   *   Get the name of an implementation class.  The name is the first
80   *   non-null value obtained from the following resources:
81   *   <ul>
82   *     <li>
83   *     The value of the (scoped) system property whose name is the same as
84   *     the SPI's fully qualified class name (as given by SPI.class.getName()).
85   *     The <code>ScopedProperties</code> class provides a way to bind
86   *     properties by classloader, in a secure hierarchy similar in concept
87   *     to the way classloader find class and resource files.
88   *     See <code>ScopedProperties</code> for more details.
89   *     <p>If the ScopedProperties are not set by users, then behaviour
90   *     is equivalent to <code>System.getProperty()</code>.
91   *     </p>
92   *     </li>
93   *     <p><li>
94   *     The value of a <code>Properties properties</code> property, if provided
95   *     as a parameter, whose name is the same as the SPI's fully qualifed class
96   *     name (as given by SPI.class.getName()).
97   *     </li></p>
98   *     <p><li>
99   *     The value obtained using the JDK1.3+ 'Service Provider' specification
100  *     (http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html) to locate a
101  *     service named <code>SPI.class.getName()</code>.  This is implemented
102  *     internally, so there is not a dependency on JDK 1.3+.
103  *     </li></p>
104  *   </ul>
105  *   </li></p>
106  *   <p><li>
107  *   If the name of the implementation class is non-null, load that class.
108  *   The class loaded is the first class loaded by the following sequence
109  *   of class loaders:
110  *   <ul>
111  *     <li>Thread Context Class Loader</li>
112  *     <li>DiscoverSingleton's Caller's Class Loader</li>
113  *     <li>SPI's Class Loader</li>
114  *     <li>DiscoverSingleton's (this class or wrapper) Class Loader</li>
115  *     <li>System Class Loader</li>
116  *   </ul>
117  *   An exception is thrown if the class cannot be loaded.
118  *   </li></p>
119  *   <p><li>
120  *   If the name of the implementation class is null, AND the default
121  *   implementation class (<code>defaultImpl</code>) is null,
122  *   then an exception is thrown.
123  *   </li></p>
124  *   <p><li>
125  *   If the name of the implementation class is null, AND the default
126  *   implementation class (<code>defaultImpl</code>) is non-null,
127  *   then load the default implementation class.  The class loaded is the
128  *   first class loaded by the following sequence of class loaders:
129  *   <ul>
130  *     <li>SPI's Class Loader</li>
131  *     <li>DiscoverSingleton's (this class or wrapper) Class Loader</li>
132  *     <li>System Class Loader</li>
133  *   </ul>
134  *   <p>
135  *   This limits the scope in which the default class loader can be found
136  *   to the SPI, DiscoverSingleton, and System class loaders.  The assumption
137  *   here is that the default implementation is closely associated with the SPI
138  *   or system, and is not defined in the user's application space.
139  *   </p>
140  *   <p>
141  *   An exception is thrown if the class cannot be loaded.
142  *   </p>
143  *   </li></p>
144  *   <p><li>
145  *   Verify that the loaded class implements the SPI: an exception is thrown
146  *   if the loaded class does not implement the SPI.
147  *   </li></p>
148  *   <p><li>
149  *   Create an instance of the class.
150  *   </li></p>
151  * </ul>
152  * 
153  * <p>
154  * Variances for various forms of the <code>find</code>
155  * methods are discussed with each such method.
156  * Variances include the following concepts:
157  * <ul>
158  *   <li><b>rootFinderClass</b> - a wrapper encapsulating a finder method
159  *   (factory or other helper class).  The root finder class is used to
160  *   determine the 'real' caller, and hence the caller's class loader -
161  *   thereby preserving knowledge that is relevant to finding the
162  *   correct/expected implementation class.
163  *   </li>
164  *   <li><b>propertiesFileName</b> - <code>Properties</code> may be specified
165  *   directly, or by property file name.  A property file is loaded using the
166  *   same sequence of class loaders used to load the SPI implementation:
167  *   <ul>
168  *     <li>Thread Context Class Loader</li>
169  *     <li>DiscoverSingleton's Caller's Class Loader</li>
170  *     <li>SPI's Class Loader</li>
171  *     <li>DiscoverSingleton's (this class) Class Loader</li>
172  *     <li>System Class Loader</li>
173  *   </ul>
174  *   </li>
175  *   <li><b>groupContext</b> - differentiates service providers for different
176  *   logical groups of service users, that might otherwise be forced to share
177  *   a common service and, more importantly, a common configuration of that
178  *   service.
179  *   <p>The groupContext is used to qualify the name of the property file
180  *   name: <code>groupContext + '.' + propertiesFileName</code>.  If that
181  *   file is not found, then the unqualified propertyFileName is used.
182  *   </p>
183  *   <p>In addition, groupContext is used to qualify the name of the system
184  *   property used to find the service implementation by prepending the value
185  *   of <code>groupContext</code> to the property name:
186  *   <code>groupContext&gt; + '.' + SPI.class.getName()</code>.
187  *   Again, if a system property cannot be found by that name, then the
188  *   unqualified property name is used.
189  *   </p>
190  *   </li>
191  * </ul>
192  * </p>
193  * 
194  * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is modelled
195  * after the SAXParserFactory and DocumentBuilderFactory implementations
196  * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.
197  * </p>
198  * 
199  * @author Richard A. Sitze
200  * @author Craig R. McClanahan
201  * @author Costin Manolache
202  * @version $Revision: 480374 $ $Date: 2006-11-28 19:33:25 -0800 (Tue, 28 Nov 2006) $
203  */
204 public class DiscoverSingleton {
205     /*********************** (RELATIVELY) SIMPLE FINDERS **********************
206      * 
207      * These finders are suitable for direct use in components looking for a
208      * service.  If you are not sure which finder(s) to use, you can narrow
209      * your search to one of these.
210      */
211     
212     /***
213      * Find implementation of SPI.
214      * 
215      * @param spiClass Service Provider Interface Class.
216      * 
217      * @return Instance of a class implementing the SPI.
218      * 
219      * @exception DiscoveryException Thrown if the name of a class implementing
220      *            the SPI cannot be found, if the class cannot be loaded and
221      *            instantiated, or if the resulting class does not implement
222      *            (or extend) the SPI.
223      */
224     public static Object find(Class spiClass)
225         throws DiscoveryException
226     {
227         return find(null,
228                     new SPInterface(spiClass),
229                     DiscoverClass.nullProperties,
230                     DiscoverClass.nullDefaultImpl);
231     }
232 
233     /***
234      * Find implementation of SPI.
235      * 
236      * @param spiClass Service Provider Interface Class.
237      * 
238      * @param properties Used to determine name of SPI implementation,
239      *                   and passed to implementation.init() method if
240      *                   implementation implements Service interface.
241      * 
242      * @return Instance of a class implementing the SPI.
243      * 
244      * @exception DiscoveryException Thrown if the name of a class implementing
245      *            the SPI cannot be found, if the class cannot be loaded and
246      *            instantiated, or if the resulting class does not implement
247      *            (or extend) the SPI.
248      */
249     public static Object find(Class spiClass, Properties properties)
250         throws DiscoveryException
251     {
252         return find(null,
253                     new SPInterface(spiClass),
254                     new PropertiesHolder(properties),
255                     DiscoverClass.nullDefaultImpl);
256     }
257 
258     /***
259      * Find implementation of SPI.
260      * 
261      * @param spiClass Service Provider Interface Class.
262      * 
263      * @param defaultImpl Default implementation.
264      * 
265      * @return Instance of a class implementing the SPI.
266      * 
267      * @exception DiscoveryException Thrown if the name of a class implementing
268      *            the SPI cannot be found, if the class cannot be loaded and
269      *            instantiated, or if the resulting class does not implement
270      *            (or extend) the SPI.
271      */
272     public static Object find(Class spiClass, String defaultImpl)
273         throws DiscoveryException
274     {
275         return find(null,
276                     new SPInterface(spiClass),
277                     DiscoverClass.nullProperties,
278                     new DefaultClassHolder(defaultImpl));
279     }
280 
281     /***
282      * Find implementation of SPI.
283      * 
284      * @param spiClass Service Provider Interface Class.
285      * 
286      * @param properties Used to determine name of SPI implementation,
287      *                   and passed to implementation.init() method if
288      *                   implementation implements Service interface.
289      * 
290      * @param defaultImpl Default implementation.
291      * 
292      * @return Instance of a class implementing the SPI.
293      * 
294      * @exception DiscoveryException Thrown if the name of a class implementing
295      *            the SPI cannot be found, if the class cannot be loaded and
296      *            instantiated, or if the resulting class does not implement
297      *            (or extend) the SPI.
298      */
299     public static Object find(Class spiClass,
300                               Properties properties,
301                               String defaultImpl)
302         throws DiscoveryException
303     {
304         return find(null,
305                     new SPInterface(spiClass),
306                     new PropertiesHolder(properties),
307                     new DefaultClassHolder(defaultImpl));
308     }
309 
310     /***
311      * Find implementation of SPI.
312      * 
313      * @param spiClass Service Provider Interface Class.
314      * 
315      * @param propertiesFileName Used to determine name of SPI implementation,
316      *                   and passed to implementation.init() method if
317      *                   implementation implements Service interface.
318      * 
319      * @param defaultImpl Default implementation.
320      * 
321      * @return Instance of a class implementing the SPI.
322      * 
323      * @exception DiscoveryException Thrown if the name of a class implementing
324      *            the SPI cannot be found, if the class cannot be loaded and
325      *            instantiated, or if the resulting class does not implement
326      *            (or extend) the SPI.
327      */
328     public static Object find(Class spiClass,
329                               String propertiesFileName,
330                               String defaultImpl)
331         throws DiscoveryException
332     {
333         return find(null,
334                     new SPInterface(spiClass),
335                     new PropertiesHolder(propertiesFileName),
336                     new DefaultClassHolder(defaultImpl));
337     }
338     
339     /**************** FINDERS FOR USE IN FACTORY/HELPER METHODS ***************
340      */
341 
342 
343     /***
344      * Find implementation of SPI.
345      * 
346      * @param spi Service Provider Interface Class.
347      * 
348      * @param properties Used to determine name of SPI implementation,
349      *                   and passed to implementation.init() method if
350      *                   implementation implements Service interface.
351      * 
352      * @param defaultImpl Default implementation.
353      * 
354      * @return Instance of a class implementing the SPI.
355      * 
356      * @exception DiscoveryException Thrown if the name of a class implementing
357      *            the SPI cannot be found, if the class cannot be loaded and
358      *            instantiated, or if the resulting class does not implement
359      *            (or extend) the SPI.
360      */
361     public static Object find(ClassLoaders loaders,
362                               SPInterface spi,
363                               PropertiesHolder properties,
364                               DefaultClassHolder defaultImpl)
365         throws DiscoveryException
366     {
367         ClassLoader contextLoader = JDKHooks.getJDKHooks().getThreadContextClassLoader();
368 
369         Object obj = get(contextLoader, spi.getSPName());
370 
371         if (obj == null) {
372             try {
373                 obj = DiscoverClass.newInstance(loaders, spi, properties, defaultImpl);
374                 
375                 if (obj != null) {
376                     put(contextLoader, spi.getSPName(), obj);
377                 }
378             } catch (DiscoveryException de) {
379                 throw de;
380             } catch (Exception e) {
381                 throw new DiscoveryException("Unable to instantiate implementation class for " + spi.getSPName(), e);
382             }
383         }
384         
385         return obj;
386     }
387 
388     /*********************** CACHE-MANAGEMENT SUPPORT **********************/
389     
390     /***
391      * Release all internal references to previously created service
392      * instances associated with the current thread context class loader.
393      * The <code>release()</code> method is called for service instances that
394      * implement the <code>Service</code> interface.
395      *
396      * This is useful in environments like servlet containers,
397      * which implement application reloading by throwing away a ClassLoader.
398      * Dangling references to objects in that class loader would prevent
399      * garbage collection.
400      */
401     public static synchronized void release() {
402         EnvironmentCache.release();
403     }
404 
405     /***
406      * Release any internal references to a previously created service
407      * instance associated with the current thread context class loader.
408      * If the SPI instance implements <code>Service</code>, then call
409      * <code>release()</code>.
410      */
411     public static synchronized void release(Class spiClass) {
412         HashMap spis = (HashMap)EnvironmentCache.get(JDKHooks.getJDKHooks().getThreadContextClassLoader());
413         
414         if (spis != null) {
415             spis.remove(spiClass.getName());
416         }
417     }
418     
419     
420     /************************** SPI CACHE SUPPORT *************************
421      * 
422      * Cache services by a 'key' unique to the requesting class/environment:
423      * 
424      * When we 'release', it is expected that the caller of the 'release'
425      * have the same thread context class loader... as that will be used
426      * to identify all cached entries to be released.
427      * 
428      * We will manage synchronization directly, so all caches are implemented
429      * as HashMap (unsynchronized).
430      * 
431      * - ClassLoader::groupContext::SPI::Instance Cache
432      *         Cache : HashMap
433      *         Key   : Thread Context Class Loader (<code>ClassLoader</code>).
434      *         Value : groupContext::SPI Cache (<code>HashMap</code>).
435      * 
436      * - groupContext::SPI::Instance Cache
437      *         Cache : HashMap
438      *         Key   : groupContext (<code>String</code>).
439      *         Value : SPI Cache (<code>HashMap</code>).
440      * 
441      * - SPI::Instance Cache
442      *         Cache : HashMap
443      *         Key   : SPI Class Name (<code>String</code>).
444      *         Value : SPI Instance/Implementation (<code>Object</code>.
445      */
446 
447     /***
448      * Implements first two levels of the cache (loader & groupContext).
449      * Allows null keys, important as default groupContext is null.
450      */
451     // FIXME: Why is this here? All the methods used are static.
452     //private static final EnvironmentCache root_cache = new EnvironmentCache();
453 
454     /***
455      * Get service keyed by spi & classLoader.
456      */
457     private static synchronized Object get(ClassLoader classLoader,
458                                            String spiName)
459     {
460         HashMap spis = (HashMap)EnvironmentCache.get(classLoader);
461         
462         return (spis != null)
463                ? spis.get(spiName)
464                : null;
465     }
466     
467     /***
468      * Put service keyed by spi & classLoader.
469      */
470     private static synchronized void put(ClassLoader classLoader,
471                                          String spiName,
472                                          Object service)
473     {
474         if (service != null)
475         {
476             HashMap spis = (HashMap)EnvironmentCache.get(classLoader);
477             
478             if (spis == null) {
479                 spis = new HashMap(EnvironmentCache.smallHashSize);
480                 EnvironmentCache.put(classLoader, spis);
481             }
482             
483             spis.put(spiName, service);
484         }
485     }
486 }