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.Map;
021 import java.util.Properties;
022
023 import org.apache.commons.discovery.DiscoveryException;
024 import org.apache.commons.discovery.jdk.JDKHooks;
025 import 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 <T> T find(Class<T> spi);</li>
059 * <li>static <T> T find(Class<T> spi, Properties properties);</li>
060 * <li>static <T> T find(Class<T> spi, String defaultImpl);</li>
061 * <li>static <T> T find(Class<T> spi,
062 * Properties properties, String defaultImpl);</li>
063 * <li>static <T> T find(Class<T> spi,
064 * String propertiesFileName, String defaultImpl);</li>
065 * <li>static <T> T find(ClassLoaders loaders, SPInterface<T> spi,
066 * PropertiesHolder holder, DefaultClassHolder<T> 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> + '.' + 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 }