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.lang.reflect.InvocationTargetException; 020 import java.util.LinkedList; 021 import java.util.List; 022 import java.util.Properties; 023 024 import org.apache.commons.discovery.DiscoveryException; 025 import org.apache.commons.discovery.ResourceClass; 026 import org.apache.commons.discovery.ResourceClassIterator; 027 import org.apache.commons.discovery.ResourceNameIterator; 028 import org.apache.commons.discovery.resource.ClassLoaders; 029 import org.apache.commons.discovery.resource.classes.DiscoverClasses; 030 import org.apache.commons.discovery.resource.names.DiscoverServiceNames; 031 032 /** 033 * <p>Discover class that implements a given service interface, 034 * with discovery and configuration features similar to that employed 035 * by standard Java APIs such as JAXP. 036 * </p> 037 * 038 * <p>In the context of this package, a service interface is defined by a 039 * Service Provider Interface (SPI). The SPI is expressed as a Java interface, 040 * abstract class, or (base) class that defines an expected programming 041 * interface. 042 * </p> 043 * 044 * <p>DiscoverClass provides the <code>find</code> methods for locating a 045 * class that implements a service interface (SPI). Each form of 046 * <code>find</code> varies slightly, but they all perform the same basic 047 * function. 048 * 049 * The <code>DiscoverClass.find</code> methods proceed as follows: 050 * </p> 051 * <ul> 052 * <p><li> 053 * Get the name of an implementation class. The name is the first 054 * non-null value obtained from the following resources: 055 * <ul> 056 * <li> 057 * The value of the (scoped) system property whose name is the same as 058 * the SPI's fully qualified class name (as given by SPI.class.getName()). 059 * The <code>ScopedProperties</code> class provides a way to bind 060 * properties by classloader, in a secure hierarchy similar in concept 061 * to the way classloader find class and resource files. 062 * See <code>ScopedProperties</code> for more details. 063 * <p>If the ScopedProperties are not set by users, then behaviour 064 * is equivalent to <code>System.getProperty()</code>. 065 * </p> 066 * </li> 067 * <p><li> 068 * The value of a <code>Properties properties</code> property, if provided 069 * as a parameter, whose name is the same as the SPI's fully qualifed class 070 * name (as given by SPI.class.getName()). 071 * </li></p> 072 * <p><li> 073 * The value obtained using the JDK1.3+ 'Service Provider' specification 074 * (http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html) to locate a 075 * service named <code>SPI.class.getName()</code>. This is implemented 076 * internally, so there is not a dependency on JDK 1.3+. 077 * </li></p> 078 * </ul> 079 * </li></p> 080 * <p><li> 081 * If the name of the implementation class is non-null, load that class. 082 * The class loaded is the first class loaded by the following sequence 083 * of class loaders: 084 * <ul> 085 * <li>Thread Context Class Loader</li> 086 * <li>DiscoverSingleton's Caller's Class Loader</li> 087 * <li>SPI's Class Loader</li> 088 * <li>DiscoverSingleton's (this class or wrapper) Class Loader</li> 089 * <li>System Class Loader</li> 090 * </ul> 091 * An exception is thrown if the class cannot be loaded. 092 * </li></p> 093 * <p><li> 094 * If the name of the implementation class is null, AND the default 095 * implementation class name (<code>defaultImpl</code>) is null, 096 * then an exception is thrown. 097 * </li></p> 098 * <p><li> 099 * If the name of the implementation class is null, AND the default 100 * implementation class (<code>defaultImpl</code>) is non-null, 101 * then load the default implementation class. The class loaded is the 102 * first class loaded by the following sequence of class loaders: 103 * <ul> 104 * <li>SPI's Class Loader</li> 105 * <li>DiscoverSingleton's (this class or wrapper) Class Loader</li> 106 * <li>System Class Loader</li> 107 * </ul> 108 * <p> 109 * This limits the scope in which the default class loader can be found 110 * to the SPI, DiscoverSingleton, and System class loaders. The assumption here 111 * is that the default implementation is closely associated with the SPI 112 * or system, and is not defined in the user's application space. 113 * </p> 114 * <p> 115 * An exception is thrown if the class cannot be loaded. 116 * </p> 117 * </li></p> 118 * <p><li> 119 * Verify that the loaded class implements the SPI: an exception is thrown 120 * if the loaded class does not implement the SPI. 121 * </li></p> 122 * </ul> 123 * </p> 124 * 125 * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is modelled 126 * after the SAXParserFactory and DocumentBuilderFactory implementations 127 * (corresponding to the JAXP pluggability APIs) found in Apache Xerces. 128 * </p> 129 * 130 * @version $Revision: 1090010 $ $Date: 2011-04-07 23:05:58 +0200 (Thu, 07 Apr 2011) $ 131 */ 132 public class DiscoverClass { 133 134 /** 135 * Readable placeholder for a null value. 136 */ 137 public static final PropertiesHolder nullProperties = null; 138 139 /** 140 * The class loaders holder. 141 */ 142 private final ClassLoaders classLoaders; 143 144 /** 145 * Create a class instance with dynamic environment 146 * (thread context class loader is determined on each call). 147 * 148 * Dynamically construct class loaders on each call. 149 */ 150 public DiscoverClass() { 151 this(null); 152 } 153 154 /** 155 * Create a class instance with dynamic environment 156 * (thread context class loader is determined on each call). 157 * 158 * Cache static list of class loaders for each call. 159 * 160 * @param classLoaders The class loaders holder 161 */ 162 public DiscoverClass(ClassLoaders classLoaders) { 163 this.classLoaders = classLoaders; 164 } 165 166 /** 167 * Return the class loaders holder for the given SPI. 168 * 169 * @param spiClass The SPI type 170 * @return The class loaders holder for the given SPI 171 */ 172 public ClassLoaders getClassLoaders(@SuppressWarnings("unused") Class<?> spiClass) { 173 return classLoaders; 174 } 175 176 /** 177 * Find class implementing SPI. 178 * 179 * @param <T> The SPI type 180 * @param <S> Any class extending T 181 * @param spiClass Service Provider Interface Class. 182 * @return Class implementing the SPI. 183 * @exception DiscoveryException Thrown if the name of a class implementing 184 * the SPI cannot be found, if the class cannot be loaded, or if 185 * the resulting class does not implement (or extend) the SPI. 186 */ 187 public <T, S extends T> Class<S> find(Class<T> spiClass) throws DiscoveryException { 188 return find(getClassLoaders(spiClass), 189 new SPInterface<T>(spiClass), 190 nullProperties, 191 (DefaultClassHolder<T>) null); 192 } 193 194 /** 195 * Find class implementing SPI. 196 * 197 * @param <T> The SPI type 198 * @param <S> Any class extending T 199 * @param spiClass Service Provider Interface Class. 200 * @param properties Used to determine name of SPI implementation. 201 * @return Class implementing the SPI. 202 * @exception DiscoveryException Thrown if the name of a class implementing 203 * the SPI cannot be found, if the class cannot be loaded, or if 204 * the resulting class does not implement (or extend) the SPI. 205 */ 206 public <T, S extends T> Class<S> find(Class<T> spiClass, Properties properties) throws DiscoveryException { 207 return find(getClassLoaders(spiClass), 208 new SPInterface<T>(spiClass), 209 new PropertiesHolder(properties), 210 (DefaultClassHolder<T>) null); 211 } 212 213 /** 214 * Find class implementing SPI. 215 * 216 * @param <T> The SPI type 217 * @param <S> Any class extending T 218 * @param spiClass Service Provider Interface Class. 219 * @param defaultImpl Default implementation name. 220 * @return Class implementing the SPI. 221 * @exception DiscoveryException Thrown if the name of a class implementing 222 * the SPI cannot be found, if the class cannot be loaded, or if 223 * the resulting class does not implement (or extend) the SPI. 224 */ 225 public <T, S extends T> Class<S> find(Class<T> spiClass, String defaultImpl) throws DiscoveryException { 226 return find(getClassLoaders(spiClass), 227 new SPInterface<T>(spiClass), 228 nullProperties, 229 new DefaultClassHolder<T>(defaultImpl)); 230 } 231 232 /** 233 * Find class implementing SPI. 234 * 235 * @param <T> The SPI type 236 * @param <S> Any class extending T 237 * @param spiClass Service Provider Interface Class. 238 * @param properties Used to determine name of SPI implementation,. 239 * @param defaultImpl Default implementation class. 240 * @return Class implementing the SPI. 241 * @exception DiscoveryException Thrown if the name of a class implementing 242 * the SPI cannot be found, if the class cannot be loaded, or if 243 * the resulting class does not implement (or extend) the SPI. 244 */ 245 public <T, S extends T> Class<S> find(Class<T> spiClass, Properties properties, String defaultImpl) 246 throws DiscoveryException { 247 return find(getClassLoaders(spiClass), 248 new SPInterface<T>(spiClass), 249 new PropertiesHolder(properties), 250 new DefaultClassHolder<T>(defaultImpl)); 251 } 252 253 /** 254 * Find class implementing SPI. 255 * 256 * @param <T> The SPI type 257 * @param <S> Any class extending T 258 * @param spiClass Service Provider Interface Class. 259 * @param propertiesFileName Used to determine name of SPI implementation,. 260 * @param defaultImpl Default implementation class. 261 * @return Class implementing the SPI. 262 * @exception DiscoveryException Thrown if the name of a class implementing 263 * the SPI cannot be found, if the class cannot be loaded, or if 264 * the resulting class does not implement (or extend) the SPI. 265 */ 266 public <T, S extends T> Class<S> find(Class<T> spiClass, String propertiesFileName, String defaultImpl) 267 throws DiscoveryException { 268 return find(getClassLoaders(spiClass), 269 new SPInterface<T>(spiClass), 270 new PropertiesHolder(propertiesFileName), 271 new DefaultClassHolder<T>(defaultImpl)); 272 } 273 274 /** 275 * Find class implementing SPI. 276 * 277 * @param <T> The SPI type 278 * @param <S> Any class extending T 279 * @param loaders The class loaders holder 280 * @param spi Service Provider Interface Class. 281 * @param properties Used to determine name of SPI implementation,. 282 * @param defaultImpl Default implementation class. 283 * @return Class implementing the SPI. 284 * @exception DiscoveryException Thrown if the name of a class implementing 285 * the SPI cannot be found, if the class cannot be loaded, or if 286 * the resulting class does not implement (or extend) the SPI. 287 */ 288 public static <T, S extends T> Class<S> find(ClassLoaders loaders, 289 SPInterface<T> spi, 290 PropertiesHolder properties, 291 DefaultClassHolder<T> defaultImpl) throws DiscoveryException { 292 if (loaders == null) { 293 loaders = ClassLoaders.getLibLoaders(spi.getSPClass(), 294 DiscoverClass.class, 295 true); 296 } 297 298 Properties props = (properties == null) 299 ? null 300 : properties.getProperties(spi, loaders); 301 302 String[] classNames = discoverClassNames(spi, props); 303 Exception error = null; 304 305 if (classNames.length > 0) { 306 DiscoverClasses<T> classDiscovery = new DiscoverClasses<T>(loaders); 307 308 for (String className : classNames) { 309 ResourceClassIterator<T> classes = 310 classDiscovery.findResourceClasses(className); 311 312 // If it's set as a property.. it had better be there! 313 if (classes.hasNext()) { 314 ResourceClass<T> info = classes.nextResourceClass(); 315 try { 316 return info.loadClass(); 317 } catch (Exception e) { 318 error = e; 319 } 320 } 321 } 322 } else { 323 ResourceNameIterator classIter = 324 (new DiscoverServiceNames(loaders)).findResourceNames(spi.getSPName()); 325 326 ResourceClassIterator<T> classes = 327 (new DiscoverClasses<T>(loaders)).findResourceClasses(classIter); 328 329 if (!classes.hasNext() && defaultImpl != null) { 330 return defaultImpl.getDefaultClass(spi, loaders); 331 } 332 333 // Services we iterate through until we find one that loads.. 334 while (classes.hasNext()) { 335 ResourceClass<T> info = classes.nextResourceClass(); 336 try { 337 return info.loadClass(); 338 } catch (Exception e) { 339 error = e; 340 } 341 } 342 } 343 344 throw new DiscoveryException("No implementation defined for " + spi.getSPName(), error); 345 // return null; 346 } 347 348 /** 349 * Create new instance of class implementing SPI. 350 * 351 * @param <T> The SPI type 352 * @param spiClass Service Provider Interface Class. 353 * @return Instance of a class implementing the SPI. 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 * @throws InstantiationException see {@link Class#newInstance()} 359 * @throws IllegalAccessException see {@link Class#newInstance()} 360 * @throws NoSuchMethodException see {@link Class#newInstance()} 361 * @throws InvocationTargetException see {@link Class#newInstance()} 362 */ 363 public <T> T newInstance(Class<T> spiClass) 364 throws DiscoveryException, 365 InstantiationException, 366 IllegalAccessException, 367 NoSuchMethodException, 368 InvocationTargetException { 369 return newInstance(getClassLoaders(spiClass), 370 new SPInterface<T>(spiClass), 371 nullProperties, 372 (DefaultClassHolder<T>) null); 373 } 374 375 /** 376 * Create new instance of class implementing SPI. 377 * 378 * @param <T> The SPI type 379 * @param spiClass Service Provider Interface Class. 380 * @param properties Used to determine name of SPI implementation, 381 * and passed to implementation.init() method if 382 * implementation implements Service interface. 383 * @return Instance of a class implementing the SPI. 384 * @exception DiscoveryException Thrown if the name of a class implementing 385 * the SPI cannot be found, if the class cannot be loaded and 386 * instantiated, or if the resulting class does not implement 387 * (or extend) the SPI. 388 * @throws InstantiationException see {@link Class#newInstance()} 389 * @throws IllegalAccessException see {@link Class#newInstance()} 390 * @throws NoSuchMethodException see {@link Class#newInstance()} 391 * @throws InvocationTargetException see {@link Class#newInstance()} 392 */ 393 public <T> T newInstance(Class<T> spiClass, Properties properties) throws DiscoveryException, 394 InstantiationException, 395 IllegalAccessException, 396 NoSuchMethodException, 397 InvocationTargetException { 398 return newInstance(getClassLoaders(spiClass), 399 new SPInterface<T>(spiClass), 400 new PropertiesHolder(properties), 401 (DefaultClassHolder<T>) null); 402 } 403 404 /** 405 * Create new instance of class implementing SPI. 406 * 407 * @param <T> The SPI type 408 * @param spiClass Service Provider Interface Class. 409 * @param defaultImpl Default implementation. 410 * @return Instance of a class implementing the SPI. 411 * @exception DiscoveryException Thrown if the name of a class implementing 412 * the SPI cannot be found, if the class cannot be loaded and 413 * instantiated, or if the resulting class does not implement 414 * (or extend) the SPI. 415 * @throws InstantiationException see {@link Class#newInstance()} 416 * @throws IllegalAccessException see {@link Class#newInstance()} 417 * @throws NoSuchMethodException see {@link Class#newInstance()} 418 * @throws InvocationTargetException see {@link Class#newInstance()} 419 */ 420 public <T> T newInstance(Class<T> spiClass, String defaultImpl) throws DiscoveryException, 421 InstantiationException, 422 IllegalAccessException, 423 NoSuchMethodException, 424 InvocationTargetException { 425 return newInstance(getClassLoaders(spiClass), 426 new SPInterface<T>(spiClass), 427 nullProperties, 428 new DefaultClassHolder<T>(defaultImpl)); 429 } 430 431 /** 432 * Create new instance of class implementing SPI. 433 * 434 * @param <T> The SPI type 435 * @param spiClass Service Provider Interface Class. 436 * @param properties Used to determine name of SPI implementation, 437 * and passed to implementation.init() method if 438 * implementation implements Service interface. 439 * @param defaultImpl Default implementation. 440 * @return Instance of a class implementing the SPI. 441 * @exception DiscoveryException Thrown if the name of a class implementing 442 * the SPI cannot be found, if the class cannot be loaded and 443 * instantiated, or if the resulting class does not implement 444 * (or extend) the SPI. 445 * @throws InstantiationException see {@link Class#newInstance()} 446 * @throws IllegalAccessException see {@link Class#newInstance()} 447 * @throws NoSuchMethodException see {@link Class#newInstance()} 448 * @throws InvocationTargetException see {@link Class#newInstance()} 449 */ 450 public <T> T newInstance(Class<T> spiClass, Properties properties, String defaultImpl) throws DiscoveryException, 451 InstantiationException, 452 IllegalAccessException, 453 NoSuchMethodException, 454 InvocationTargetException { 455 return newInstance(getClassLoaders(spiClass), 456 new SPInterface<T>(spiClass), 457 new PropertiesHolder(properties), 458 new DefaultClassHolder<T>(defaultImpl)); 459 } 460 461 /** 462 * Create new instance of class implementing SPI. 463 * 464 * @param <T> The SPI type 465 * @param spiClass Service Provider Interface Class. 466 * @param propertiesFileName Used to determine name of SPI implementation, 467 * and passed to implementation.init() method if 468 * implementation implements Service interface. 469 * @param defaultImpl Default implementation. 470 * @return Instance of a class implementing the SPI. 471 * @exception DiscoveryException Thrown if the name of a class implementing 472 * the SPI cannot be found, if the class cannot be loaded and 473 * instantiated, or if the resulting class does not implement 474 * (or extend) the SPI. 475 * @throws InstantiationException see {@link Class#newInstance()} 476 * @throws IllegalAccessException see {@link Class#newInstance()} 477 * @throws NoSuchMethodException see {@link Class#newInstance()} 478 * @throws InvocationTargetException see {@link Class#newInstance()} 479 */ 480 public <T> T newInstance(Class<T> spiClass, String propertiesFileName, String defaultImpl) 481 throws DiscoveryException, 482 InstantiationException, 483 IllegalAccessException, 484 NoSuchMethodException, 485 InvocationTargetException { 486 return newInstance(getClassLoaders(spiClass), 487 new SPInterface<T>(spiClass), 488 new PropertiesHolder(propertiesFileName), 489 new DefaultClassHolder<T>(defaultImpl)); 490 } 491 492 /** 493 * Create new instance of class implementing SPI. 494 * 495 * @param <T> The SPI type 496 * @param loaders The class loaders holder 497 * @param spi Service Provider Interface Class. 498 * @param properties Used to determine name of SPI implementation, 499 * and passed to implementation.init() method if 500 * implementation implements Service interface. 501 * @param defaultImpl Default implementation. 502 * @return Instance of a class implementing the SPI. 503 * @exception DiscoveryException Thrown if the name of a class implementing 504 * the SPI cannot be found, if the class cannot be loaded and 505 * instantiated, or if the resulting class does not implement 506 * (or extend) the SPI. 507 * @throws InstantiationException see {@link Class#newInstance()} 508 * @throws IllegalAccessException see {@link Class#newInstance()} 509 * @throws NoSuchMethodException see {@link Class#newInstance()} 510 * @throws InvocationTargetException see {@link Class#newInstance()} 511 */ 512 public static <T> T newInstance(ClassLoaders loaders, 513 SPInterface<T> spi, 514 PropertiesHolder properties, 515 DefaultClassHolder<T> defaultImpl) throws DiscoveryException, 516 InstantiationException, 517 IllegalAccessException, 518 NoSuchMethodException, 519 InvocationTargetException { 520 return spi.newInstance(find(loaders, spi, properties, defaultImpl)); 521 } 522 523 /** 524 * <p>Discover names of SPI implementation Classes from properties. 525 * The names are the non-null values, in order, obtained from the following 526 * resources: 527 * <ul> 528 * <li>ManagedProperty.getProperty(SPI.class.getName());</li> 529 * <li>properties.getProperty(SPI.class.getName());</li> 530 * </ul> 531 * 532 * @param <T> The SPI type 533 * @param spi The SPI representation 534 * @param properties Properties that may define the implementation 535 * class name(s). 536 * @return String[] Name of classes implementing the SPI. 537 * @exception DiscoveryException Thrown if the name of a class implementing 538 * the SPI cannot be found. 539 */ 540 public static <T> String[] discoverClassNames(SPInterface<T> spi, 541 Properties properties) { 542 List<String> names = new LinkedList<String>(); 543 544 String spiName = spi.getSPName(); 545 String propertyName = spi.getPropertyName(); 546 547 boolean includeAltProperty = !spiName.equals(propertyName); 548 549 // Try the (managed) system property spiName 550 String className = getManagedProperty(spiName); 551 if (className != null) { 552 names.add(className); 553 } 554 555 if (includeAltProperty) { 556 // Try the (managed) system property propertyName 557 className = getManagedProperty(propertyName); 558 if (className != null) { 559 names.add(className); 560 } 561 } 562 563 if (properties != null) { 564 // Try the properties parameter spiName 565 className = properties.getProperty(spiName); 566 if (className != null) { 567 names.add(className); 568 } 569 570 if (includeAltProperty) { 571 // Try the properties parameter propertyName 572 className = properties.getProperty(propertyName); 573 if (className != null) { 574 names.add(className); 575 } 576 } 577 } 578 579 String[] results = new String[names.size()]; 580 names.toArray(results); 581 582 return results; 583 } 584 585 /** 586 * Load the class whose name is given by the value of a (Managed) 587 * System Property. 588 * 589 * @param propertyName the name of the system property whose value is 590 * the name of the class to load. 591 * @return The managed property value 592 * @see ManagedProperties 593 */ 594 public static String getManagedProperty(String propertyName) { 595 String value; 596 try { 597 value = ManagedProperties.getProperty(propertyName); 598 } catch (SecurityException e) { 599 value = null; 600 } 601 return value; 602 } 603 604 }