1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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> + '.' + 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
452
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 }