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: */
017: package org.apache.commons.discovery.tools;
018:
019: import java.util.HashMap;
020: import java.util.Properties;
021:
022: import org.apache.commons.discovery.DiscoveryException;
023: import org.apache.commons.discovery.jdk.JDKHooks;
024: import org.apache.commons.discovery.resource.ClassLoaders;
025:
026: /**
027: * <p>Discover singleton service providers.
028: * This
029: * </p>
030: *
031: * <p>DiscoverSingleton instances are cached by the Discovery service,
032: * keyed by a combination of
033: * <ul>
034: * <li>thread context class loader,</li>
035: * <li>groupContext, and</li>
036: * <li>SPI.</li>
037: * </ul>
038: * This DOES allow multiple instances of a given <i>singleton</i> class
039: * to exist for different class loaders and different group contexts.
040: * </p>
041: *
042: * <p>In the context of this package, a service interface is defined by a
043: * Service Provider Interface (SPI). The SPI is expressed as a Java interface,
044: * abstract class, or (base) class that defines an expected programming
045: * interface.
046: * </p>
047: *
048: * <p>DiscoverSingleton provides the <code>find</code> methods for locating and
049: * instantiating a singleton instance of an implementation of a service (SPI).
050: * Each form of <code>find</code> varies slightly, but they all perform the
051: * same basic function.
052: *
053: * The simplest <code>find</code> methods are intended for direct use by
054: * components looking for a service. If you are not sure which finder(s)
055: * to use, you can narrow your search to one of these:
056: * <ul>
057: * <li>static Object find(Class spi);</li>
058: * <li>static Object find(Class spi, Properties properties);</li>
059: * <li>static Object find(Class spi, String defaultImpl);</li>
060: * <li>static Object find(Class spi,
061: * Properties properties, String defaultImpl);</li>
062: * <li>static Object find(Class spi,
063: * String propertiesFileName, String defaultImpl);</li>
064: * <li>static Object find(String groupContext, Class spi,
065: * Properties properties, String defaultImpl);</li>
066: * <li>static Object find(String groupContext, Class spi,
067: * String propertiesFileName, String defaultImpl);</li>
068: * </ul>
069: *
070: * The <code>DiscoverSingleton.find</code> methods proceed as follows:
071: * </p>
072: * <ul>
073: * <p><li>
074: * Examine an internal cache to determine if the desired service was
075: * previously identified and instantiated. If found in cache, return it.
076: * </li></p>
077: * <p><li>
078: * Get the name of an implementation class. The name is the first
079: * non-null value obtained from the following resources:
080: * <ul>
081: * <li>
082: * The value of the (scoped) system property whose name is the same as
083: * the SPI's fully qualified class name (as given by SPI.class.getName()).
084: * The <code>ScopedProperties</code> class provides a way to bind
085: * properties by classloader, in a secure hierarchy similar in concept
086: * to the way classloader find class and resource files.
087: * See <code>ScopedProperties</code> for more details.
088: * <p>If the ScopedProperties are not set by users, then behaviour
089: * is equivalent to <code>System.getProperty()</code>.
090: * </p>
091: * </li>
092: * <p><li>
093: * The value of a <code>Properties properties</code> property, if provided
094: * as a parameter, whose name is the same as the SPI's fully qualifed class
095: * name (as given by SPI.class.getName()).
096: * </li></p>
097: * <p><li>
098: * The value obtained using the JDK1.3+ 'Service Provider' specification
099: * (http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html) to locate a
100: * service named <code>SPI.class.getName()</code>. This is implemented
101: * internally, so there is not a dependency on JDK 1.3+.
102: * </li></p>
103: * </ul>
104: * </li></p>
105: * <p><li>
106: * If the name of the implementation class is non-null, load that class.
107: * The class loaded is the first class loaded by the following sequence
108: * of class loaders:
109: * <ul>
110: * <li>Thread Context Class Loader</li>
111: * <li>DiscoverSingleton's Caller's Class Loader</li>
112: * <li>SPI's Class Loader</li>
113: * <li>DiscoverSingleton's (this class or wrapper) Class Loader</li>
114: * <li>System Class Loader</li>
115: * </ul>
116: * An exception is thrown if the class cannot be loaded.
117: * </li></p>
118: * <p><li>
119: * If the name of the implementation class is null, AND the default
120: * implementation class (<code>defaultImpl</code>) is null,
121: * then an exception is thrown.
122: * </li></p>
123: * <p><li>
124: * If the name of the implementation class is null, AND the default
125: * implementation class (<code>defaultImpl</code>) is non-null,
126: * then load the default implementation class. The class loaded is the
127: * first class loaded by the following sequence of class loaders:
128: * <ul>
129: * <li>SPI's Class Loader</li>
130: * <li>DiscoverSingleton's (this class or wrapper) Class Loader</li>
131: * <li>System Class Loader</li>
132: * </ul>
133: * <p>
134: * This limits the scope in which the default class loader can be found
135: * to the SPI, DiscoverSingleton, and System class loaders. The assumption
136: * here is that the default implementation is closely associated with the SPI
137: * or system, and is not defined in the user's application space.
138: * </p>
139: * <p>
140: * An exception is thrown if the class cannot be loaded.
141: * </p>
142: * </li></p>
143: * <p><li>
144: * Verify that the loaded class implements the SPI: an exception is thrown
145: * if the loaded class does not implement the SPI.
146: * </li></p>
147: * <p><li>
148: * Create an instance of the class.
149: * </li></p>
150: * </ul>
151: *
152: * <p>
153: * Variances for various forms of the <code>find</code>
154: * methods are discussed with each such method.
155: * Variances include the following concepts:
156: * <ul>
157: * <li><b>rootFinderClass</b> - a wrapper encapsulating a finder method
158: * (factory or other helper class). The root finder class is used to
159: * determine the 'real' caller, and hence the caller's class loader -
160: * thereby preserving knowledge that is relevant to finding the
161: * correct/expected implementation class.
162: * </li>
163: * <li><b>propertiesFileName</b> - <code>Properties</code> may be specified
164: * directly, or by property file name. A property file is loaded using the
165: * same sequence of class loaders used to load the SPI implementation:
166: * <ul>
167: * <li>Thread Context Class Loader</li>
168: * <li>DiscoverSingleton's Caller's Class Loader</li>
169: * <li>SPI's Class Loader</li>
170: * <li>DiscoverSingleton's (this class) Class Loader</li>
171: * <li>System Class Loader</li>
172: * </ul>
173: * </li>
174: * <li><b>groupContext</b> - differentiates service providers for different
175: * logical groups of service users, that might otherwise be forced to share
176: * a common service and, more importantly, a common configuration of that
177: * service.
178: * <p>The groupContext is used to qualify the name of the property file
179: * name: <code>groupContext + '.' + propertiesFileName</code>. If that
180: * file is not found, then the unqualified propertyFileName is used.
181: * </p>
182: * <p>In addition, groupContext is used to qualify the name of the system
183: * property used to find the service implementation by prepending the value
184: * of <code>groupContext</code> to the property name:
185: * <code>groupContext> + '.' + SPI.class.getName()</code>.
186: * Again, if a system property cannot be found by that name, then the
187: * unqualified property name is used.
188: * </p>
189: * </li>
190: * </ul>
191: * </p>
192: *
193: * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is modelled
194: * after the SAXParserFactory and DocumentBuilderFactory implementations
195: * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.
196: * </p>
197: *
198: * @author Richard A. Sitze
199: * @author Craig R. McClanahan
200: * @author Costin Manolache
201: * @version $Revision: 480374 $ $Date: 2006-11-28 19:33:25 -0800 (Tue, 28 Nov 2006) $
202: */
203: public class DiscoverSingleton {
204: /********************** (RELATIVELY) SIMPLE FINDERS **********************
205: *
206: * These finders are suitable for direct use in components looking for a
207: * service. If you are not sure which finder(s) to use, you can narrow
208: * your search to one of these.
209: */
210:
211: /**
212: * Find implementation of SPI.
213: *
214: * @param spiClass Service Provider Interface Class.
215: *
216: * @return Instance of a class implementing the SPI.
217: *
218: * @exception DiscoveryException Thrown if the name of a class implementing
219: * the SPI cannot be found, if the class cannot be loaded and
220: * instantiated, or if the resulting class does not implement
221: * (or extend) the SPI.
222: */
223: public static Object find(Class spiClass) throws DiscoveryException {
224: return find(null, new SPInterface(spiClass),
225: DiscoverClass.nullProperties,
226: DiscoverClass.nullDefaultImpl);
227: }
228:
229: /**
230: * Find implementation of SPI.
231: *
232: * @param spiClass Service Provider Interface Class.
233: *
234: * @param properties Used to determine name of SPI implementation,
235: * and passed to implementation.init() method if
236: * implementation implements Service interface.
237: *
238: * @return Instance of a class implementing the SPI.
239: *
240: * @exception DiscoveryException Thrown if the name of a class implementing
241: * the SPI cannot be found, if the class cannot be loaded and
242: * instantiated, or if the resulting class does not implement
243: * (or extend) the SPI.
244: */
245: public static Object find(Class spiClass, Properties properties)
246: throws DiscoveryException {
247: return find(null, new SPInterface(spiClass),
248: new PropertiesHolder(properties),
249: DiscoverClass.nullDefaultImpl);
250: }
251:
252: /**
253: * Find implementation of SPI.
254: *
255: * @param spiClass Service Provider Interface Class.
256: *
257: * @param defaultImpl Default implementation.
258: *
259: * @return Instance of a class implementing the SPI.
260: *
261: * @exception DiscoveryException Thrown if the name of a class implementing
262: * the SPI cannot be found, if the class cannot be loaded and
263: * instantiated, or if the resulting class does not implement
264: * (or extend) the SPI.
265: */
266: public static Object find(Class spiClass, String defaultImpl)
267: throws DiscoveryException {
268: return find(null, new SPInterface(spiClass),
269: DiscoverClass.nullProperties, new DefaultClassHolder(
270: defaultImpl));
271: }
272:
273: /**
274: * Find implementation of SPI.
275: *
276: * @param spiClass Service Provider Interface Class.
277: *
278: * @param properties Used to determine name of SPI implementation,
279: * and passed to implementation.init() method if
280: * implementation implements Service interface.
281: *
282: * @param defaultImpl Default implementation.
283: *
284: * @return Instance of a class implementing the SPI.
285: *
286: * @exception DiscoveryException Thrown if the name of a class implementing
287: * the SPI cannot be found, if the class cannot be loaded and
288: * instantiated, or if the resulting class does not implement
289: * (or extend) the SPI.
290: */
291: public static Object find(Class spiClass, Properties properties,
292: String defaultImpl) throws DiscoveryException {
293: return find(null, new SPInterface(spiClass),
294: new PropertiesHolder(properties),
295: new DefaultClassHolder(defaultImpl));
296: }
297:
298: /**
299: * Find implementation of SPI.
300: *
301: * @param spiClass Service Provider Interface Class.
302: *
303: * @param propertiesFileName Used to determine name of SPI implementation,
304: * and passed to implementation.init() method if
305: * implementation implements Service interface.
306: *
307: * @param defaultImpl Default implementation.
308: *
309: * @return Instance of a class implementing the SPI.
310: *
311: * @exception DiscoveryException Thrown if the name of a class implementing
312: * the SPI cannot be found, if the class cannot be loaded and
313: * instantiated, or if the resulting class does not implement
314: * (or extend) the SPI.
315: */
316: public static Object find(Class spiClass,
317: String propertiesFileName, String defaultImpl)
318: throws DiscoveryException {
319: return find(null, new SPInterface(spiClass),
320: new PropertiesHolder(propertiesFileName),
321: new DefaultClassHolder(defaultImpl));
322: }
323:
324: /*************** FINDERS FOR USE IN FACTORY/HELPER METHODS ***************
325: */
326:
327: /**
328: * Find implementation of SPI.
329: *
330: * @param spi Service Provider Interface Class.
331: *
332: * @param properties Used to determine name of SPI implementation,
333: * and passed to implementation.init() method if
334: * implementation implements Service interface.
335: *
336: * @param defaultImpl Default implementation.
337: *
338: * @return Instance of a class implementing the SPI.
339: *
340: * @exception DiscoveryException Thrown if the name of a class implementing
341: * the SPI cannot be found, if the class cannot be loaded and
342: * instantiated, or if the resulting class does not implement
343: * (or extend) the SPI.
344: */
345: public static Object find(ClassLoaders loaders, SPInterface spi,
346: PropertiesHolder properties, DefaultClassHolder defaultImpl)
347: throws DiscoveryException {
348: ClassLoader contextLoader = JDKHooks.getJDKHooks()
349: .getThreadContextClassLoader();
350:
351: Object obj = get(contextLoader, spi.getSPName());
352:
353: if (obj == null) {
354: try {
355: obj = DiscoverClass.newInstance(loaders, spi,
356: properties, defaultImpl);
357:
358: if (obj != null) {
359: put(contextLoader, spi.getSPName(), obj);
360: }
361: } catch (DiscoveryException de) {
362: throw de;
363: } catch (Exception e) {
364: throw new DiscoveryException(
365: "Unable to instantiate implementation class for "
366: + spi.getSPName(), e);
367: }
368: }
369:
370: return obj;
371: }
372:
373: /********************** CACHE-MANAGEMENT SUPPORT **********************/
374:
375: /**
376: * Release all internal references to previously created service
377: * instances associated with the current thread context class loader.
378: * The <code>release()</code> method is called for service instances that
379: * implement the <code>Service</code> interface.
380: *
381: * This is useful in environments like servlet containers,
382: * which implement application reloading by throwing away a ClassLoader.
383: * Dangling references to objects in that class loader would prevent
384: * garbage collection.
385: */
386: public static synchronized void release() {
387: EnvironmentCache.release();
388: }
389:
390: /**
391: * Release any internal references to a previously created service
392: * instance associated with the current thread context class loader.
393: * If the SPI instance implements <code>Service</code>, then call
394: * <code>release()</code>.
395: */
396: public static synchronized void release(Class spiClass) {
397: HashMap spis = (HashMap) EnvironmentCache.get(JDKHooks
398: .getJDKHooks().getThreadContextClassLoader());
399:
400: if (spis != null) {
401: spis.remove(spiClass.getName());
402: }
403: }
404:
405: /************************* SPI CACHE SUPPORT *************************
406: *
407: * Cache services by a 'key' unique to the requesting class/environment:
408: *
409: * When we 'release', it is expected that the caller of the 'release'
410: * have the same thread context class loader... as that will be used
411: * to identify all cached entries to be released.
412: *
413: * We will manage synchronization directly, so all caches are implemented
414: * as HashMap (unsynchronized).
415: *
416: * - ClassLoader::groupContext::SPI::Instance Cache
417: * Cache : HashMap
418: * Key : Thread Context Class Loader (<code>ClassLoader</code>).
419: * Value : groupContext::SPI Cache (<code>HashMap</code>).
420: *
421: * - groupContext::SPI::Instance Cache
422: * Cache : HashMap
423: * Key : groupContext (<code>String</code>).
424: * Value : SPI Cache (<code>HashMap</code>).
425: *
426: * - SPI::Instance Cache
427: * Cache : HashMap
428: * Key : SPI Class Name (<code>String</code>).
429: * Value : SPI Instance/Implementation (<code>Object</code>.
430: */
431:
432: /**
433: * Implements first two levels of the cache (loader & groupContext).
434: * Allows null keys, important as default groupContext is null.
435: */
436: // FIXME: Why is this here? All the methods used are static.
437: //private static final EnvironmentCache root_cache = new EnvironmentCache();
438: /**
439: * Get service keyed by spi & classLoader.
440: */
441: private static synchronized Object get(ClassLoader classLoader,
442: String spiName) {
443: HashMap spis = (HashMap) EnvironmentCache.get(classLoader);
444:
445: return (spis != null) ? spis.get(spiName) : null;
446: }
447:
448: /**
449: * Put service keyed by spi & classLoader.
450: */
451: private static synchronized void put(ClassLoader classLoader,
452: String spiName, Object service) {
453: if (service != null) {
454: HashMap spis = (HashMap) EnvironmentCache.get(classLoader);
455:
456: if (spis == null) {
457: spis = new HashMap(EnvironmentCache.smallHashSize);
458: EnvironmentCache.put(classLoader, spis);
459: }
460:
461: spis.put(spiName, service);
462: }
463: }
464: }
|