001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.discovery.tools;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Properties;
022
023import org.apache.commons.discovery.DiscoveryException;
024import org.apache.commons.discovery.jdk.JDKHooks;
025import org.apache.commons.discovery.resource.ClassLoaders;
026
027/**
028 * <p>Discover singleton service providers.
029 * This 
030 * </p>
031 *
032 * <p>DiscoverSingleton instances are cached by the Discovery service,
033 * keyed by a combination of
034 * <ul>
035 *   <li>thread context class loader,</li>
036 *   <li>groupContext, and</li>
037 *   <li>SPI.</li>
038 * </ul>
039 * This DOES allow multiple instances of a given <i>singleton</i> class
040 * to exist for different class loaders and different group contexts.
041 * </p>
042 *
043 * <p>In the context of this package, a service interface is defined by a
044 * Service Provider Interface (SPI).  The SPI is expressed as a Java interface,
045 * abstract class, or (base) class that defines an expected programming
046 * interface.
047 * </p>
048 *
049 * <p>DiscoverSingleton provides the <code>find</code> methods for locating and
050 * instantiating a singleton instance of an implementation of a service (SPI).
051 * Each form of <code>find</code> varies slightly, but they all perform the
052 * same basic function.
053 *
054 * The simplest <code>find</code> methods are intended for direct use by
055 * components looking for a service.  If you are not sure which finder(s)
056 * to use, you can narrow your search to one of these:
057 * <ul>
058 * <li>static &lt;T&gt; T find(Class&lt;T&gt; spi);</li>
059 * <li>static &lt;T&gt; T find(Class&lt;T&gt; spi, Properties properties);</li>
060 * <li>static &lt;T&gt; T find(Class&lt;T&gt; spi, String defaultImpl);</li>
061 * <li>static &lt;T&gt; T find(Class&lt;T&gt; spi,
062 *                        Properties properties, String defaultImpl);</li>
063 * <li>static &lt;T&gt; T find(Class&lt;T&gt; spi,
064 *                        String propertiesFileName, String defaultImpl);</li>
065 * <li>static &lt;T&gt; T find(ClassLoaders loaders, SPInterface&lt;T&gt; spi,
066 *                        PropertiesHolder holder, DefaultClassHolder&lt;T&gt; holder);</li>
067 * </ul>
068 *
069 * The <code>DiscoverSingleton.find</code> methods proceed as follows:
070 * </p>
071 * <ul>
072 *   <p><li>
073 *   Examine an internal cache to determine if the desired service was
074 *   previously identified and instantiated.  If found in cache, return it.
075 *   </li></p>
076 *   <p><li>
077 *   Get the name of an implementation class.  The name is the first
078 *   non-null value obtained from the following resources:
079 *   <ul>
080 *     <li>
081 *     The value of the (scoped) system property whose name is the same as
082 *     the SPI's fully qualified class name (as given by SPI.class.getName()).
083 *     The <code>ScopedProperties</code> class provides a way to bind
084 *     properties by classloader, in a secure hierarchy similar in concept
085 *     to the way classloader find class and resource files.
086 *     See <code>ScopedProperties</code> for more details.
087 *     <p>If the ScopedProperties are not set by users, then behaviour
088 *     is equivalent to <code>System.getProperty()</code>.
089 *     </p>
090 *     </li>
091 *     <p><li>
092 *     The value of a <code>Properties properties</code> property, if provided
093 *     as a parameter, whose name is the same as the SPI's fully qualifed class
094 *     name (as given by SPI.class.getName()).
095 *     </li></p>
096 *     <p><li>
097 *     The value obtained using the JDK1.3+ 'Service Provider' specification
098 *     (http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html) to locate a
099 *     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 */
199public 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}