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