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 /** 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 Object find(Class spi);</li> 059 * <li>static Object find(Class spi, Properties properties);</li> 060 * <li>static Object find(Class spi, String defaultImpl);</li> 061 * <li>static Object find(Class spi, 062 * Properties properties, String defaultImpl);</li> 063 * <li>static Object find(Class spi, 064 * String propertiesFileName, String defaultImpl);</li> 065 * <li>static Object find(String groupContext, Class spi, 066 * Properties properties, String defaultImpl);</li> 067 * <li>static Object find(String groupContext, Class spi, 068 * String propertiesFileName, String defaultImpl);</li> 069 * </ul> 070 * 071 * The <code>DiscoverSingleton.find</code> methods proceed as follows: 072 * </p> 073 * <ul> 074 * <p><li> 075 * Examine an internal cache to determine if the desired service was 076 * previously identified and instantiated. If found in cache, return it. 077 * </li></p> 078 * <p><li> 079 * Get the name of an implementation class. The name is the first 080 * non-null value obtained from the following resources: 081 * <ul> 082 * <li> 083 * The value of the (scoped) system property whose name is the same as 084 * the SPI's fully qualified class name (as given by SPI.class.getName()). 085 * The <code>ScopedProperties</code> class provides a way to bind 086 * properties by classloader, in a secure hierarchy similar in concept 087 * to the way classloader find class and resource files. 088 * See <code>ScopedProperties</code> for more details. 089 * <p>If the ScopedProperties are not set by users, then behaviour 090 * is equivalent to <code>System.getProperty()</code>. 091 * </p> 092 * </li> 093 * <p><li> 094 * The value of a <code>Properties properties</code> property, if provided 095 * as a parameter, whose name is the same as the SPI's fully qualifed class 096 * name (as given by SPI.class.getName()). 097 * </li></p> 098 * <p><li> 099 * 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-29 04:33:25 +0100 (Mi, 29. 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 // FIXME: Why is this here? All the methods used are static. 452 //private static final EnvironmentCache root_cache = new EnvironmentCache(); 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 }