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 }