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.Properties; 021 import java.util.Vector; 022 023 import org.apache.commons.discovery.DiscoveryException; 024 import org.apache.commons.discovery.ResourceClass; 025 import org.apache.commons.discovery.ResourceClassIterator; 026 import org.apache.commons.discovery.ResourceNameIterator; 027 import org.apache.commons.discovery.resource.ClassLoaders; 028 import org.apache.commons.discovery.resource.classes.DiscoverClasses; 029 import org.apache.commons.discovery.resource.names.DiscoverServiceNames; 030 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 * @author Richard A. Sitze 131 * @author Craig R. McClanahan 132 * @author Costin Manolache 133 * @version $Revision: 480374 $ $Date: 2006-11-29 04:33:25 +0100 (Mi, 29. Nov 2006) $ 134 */ 135 public class DiscoverClass { 136 /** 137 * Readable placeholder for a null value. 138 */ 139 public static final DefaultClassHolder nullDefaultImpl = null; 140 141 /** 142 * Readable placeholder for a null value. 143 */ 144 public static final PropertiesHolder nullProperties = null; 145 146 147 private ClassLoaders classLoaders = null; 148 149 150 /** 151 * Create a class instance with dynamic environment 152 * (thread context class loader is determined on each call). 153 * 154 * Dynamically construct class loaders on each call. 155 */ 156 public DiscoverClass() { 157 this(null); 158 } 159 160 /** 161 * Create a class instance with dynamic environment 162 * (thread context class loader is determined on each call). 163 * 164 * Cache static list of class loaders for each call. 165 */ 166 public DiscoverClass(ClassLoaders classLoaders) { 167 this.classLoaders = classLoaders; 168 } 169 170 171 public ClassLoaders getClassLoaders(Class spiClass) { 172 return classLoaders; 173 } 174 175 176 /** 177 * Find class implementing SPI. 178 * 179 * @param spiClass Service Provider Interface Class. 180 * 181 * @return Class implementing the SPI. 182 * 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 Class find(Class spiClass) 188 throws DiscoveryException 189 { 190 return find(getClassLoaders(spiClass), 191 new SPInterface(spiClass), 192 nullProperties, 193 nullDefaultImpl); 194 } 195 196 /** 197 * Find class implementing SPI. 198 * 199 * @param spiClass Service Provider Interface Class. 200 * 201 * @param properties Used to determine name of SPI implementation. 202 * 203 * @return Class implementing the SPI. 204 * 205 * @exception DiscoveryException Thrown if the name of a class implementing 206 * the SPI cannot be found, if the class cannot be loaded, or if 207 * the resulting class does not implement (or extend) the SPI. 208 */ 209 public Class find(Class spiClass, Properties properties) 210 throws DiscoveryException 211 { 212 return find(getClassLoaders(spiClass), 213 new SPInterface(spiClass), 214 new PropertiesHolder(properties), 215 nullDefaultImpl); 216 } 217 218 /** 219 * Find class implementing SPI. 220 * 221 * @param spiClass Service Provider Interface Class. 222 * 223 * @param defaultImpl Default implementation name. 224 * 225 * @return Class implementing the SPI. 226 * 227 * @exception DiscoveryException Thrown if the name of a class implementing 228 * the SPI cannot be found, if the class cannot be loaded, or if 229 * the resulting class does not implement (or extend) the SPI. 230 */ 231 public Class find(Class spiClass, String defaultImpl) 232 throws DiscoveryException 233 { 234 return find(getClassLoaders(spiClass), 235 new SPInterface(spiClass), 236 nullProperties, 237 new DefaultClassHolder(defaultImpl)); 238 } 239 240 /** 241 * Find class implementing SPI. 242 * 243 * @param spiClass Service Provider Interface Class. 244 * 245 * @param properties Used to determine name of SPI implementation,. 246 * 247 * @param defaultImpl Default implementation class. 248 * 249 * @return Class implementing the SPI. 250 * 251 * @exception DiscoveryException Thrown if the name of a class implementing 252 * the SPI cannot be found, if the class cannot be loaded, or if 253 * the resulting class does not implement (or extend) the SPI. 254 */ 255 public Class find(Class spiClass, Properties properties, String defaultImpl) 256 throws DiscoveryException 257 { 258 return find(getClassLoaders(spiClass), 259 new SPInterface(spiClass), 260 new PropertiesHolder(properties), 261 new DefaultClassHolder(defaultImpl)); 262 } 263 264 /** 265 * Find class implementing SPI. 266 * 267 * @param spiClass Service Provider Interface Class. 268 * 269 * @param propertiesFileName Used to determine name of SPI implementation,. 270 * 271 * @param defaultImpl Default implementation class. 272 * 273 * @return Class implementing the SPI. 274 * 275 * @exception DiscoveryException Thrown if the name of a class implementing 276 * the SPI cannot be found, if the class cannot be loaded, or if 277 * the resulting class does not implement (or extend) the SPI. 278 */ 279 public Class find(Class spiClass, String propertiesFileName, String defaultImpl) 280 throws DiscoveryException 281 { 282 return find(getClassLoaders(spiClass), 283 new SPInterface(spiClass), 284 new PropertiesHolder(propertiesFileName), 285 new DefaultClassHolder(defaultImpl)); 286 } 287 288 /** 289 * Find class implementing SPI. 290 * 291 * @param spi Service Provider Interface Class. 292 * 293 * @param properties Used to determine name of SPI implementation,. 294 * 295 * @param defaultImpl Default implementation class. 296 * 297 * @return Class implementing the SPI. 298 * 299 * @exception DiscoveryException Thrown if the name of a class implementing 300 * the SPI cannot be found, if the class cannot be loaded, or if 301 * the resulting class does not implement (or extend) the SPI. 302 */ 303 public static Class find(ClassLoaders loaders, 304 SPInterface spi, 305 PropertiesHolder properties, 306 DefaultClassHolder defaultImpl) 307 throws DiscoveryException 308 { 309 if (loaders == null) { 310 loaders = ClassLoaders.getLibLoaders(spi.getSPClass(), 311 DiscoverClass.class, 312 true); 313 } 314 315 Properties props = (properties == null) 316 ? null 317 : properties.getProperties(spi, loaders); 318 319 String[] classNames = discoverClassNames(spi, props); 320 321 if (classNames.length > 0) { 322 DiscoverClasses classDiscovery = new DiscoverClasses(loaders); 323 324 ResourceClassIterator classes = 325 classDiscovery.findResourceClasses(classNames[0]); 326 327 // If it's set as a property.. it had better be there! 328 if (classes.hasNext()) { 329 ResourceClass info = classes.nextResourceClass(); 330 try { 331 return info.loadClass(); 332 } catch (Exception e) { 333 // ignore 334 } 335 } 336 } else { 337 ResourceNameIterator classIter = 338 (new DiscoverServiceNames(loaders)).findResourceNames(spi.getSPName()); 339 340 ResourceClassIterator classes = 341 (new DiscoverClasses(loaders)).findResourceClasses(classIter); 342 343 344 if (!classes.hasNext() && defaultImpl != null) { 345 return defaultImpl.getDefaultClass(spi, loaders); 346 } 347 348 // Services we iterate through until we find one that loads.. 349 while (classes.hasNext()) { 350 ResourceClass info = classes.nextResourceClass(); 351 try { 352 return info.loadClass(); 353 } catch (Exception e) { 354 // ignore 355 } 356 } 357 } 358 359 throw new DiscoveryException("No implementation defined for " + spi.getSPName()); 360 // return null; 361 } 362 363 /** 364 * Create new instance of class implementing SPI. 365 * 366 * @param spiClass Service Provider Interface Class. 367 * 368 * @return Instance of a class implementing the SPI. 369 * 370 * @exception DiscoveryException Thrown if the name of a class implementing 371 * the SPI cannot be found, if the class cannot be loaded and 372 * instantiated, or if the resulting class does not implement 373 * (or extend) the SPI. 374 */ 375 public Object newInstance(Class spiClass) 376 throws DiscoveryException, 377 InstantiationException, 378 IllegalAccessException, 379 NoSuchMethodException, 380 InvocationTargetException 381 { 382 return newInstance(getClassLoaders(spiClass), 383 new SPInterface(spiClass), 384 nullProperties, 385 nullDefaultImpl); 386 } 387 388 /** 389 * Create new instance of class implementing SPI. 390 * 391 * @param spiClass Service Provider Interface Class. 392 * 393 * @param properties Used to determine name of SPI implementation, 394 * and passed to implementation.init() method if 395 * implementation implements Service interface. 396 * 397 * @return Instance of a class implementing the SPI. 398 * 399 * @exception DiscoveryException Thrown if the name of a class implementing 400 * the SPI cannot be found, if the class cannot be loaded and 401 * instantiated, or if the resulting class does not implement 402 * (or extend) the SPI. 403 */ 404 public Object newInstance(Class spiClass, Properties properties) 405 throws DiscoveryException, 406 InstantiationException, 407 IllegalAccessException, 408 NoSuchMethodException, 409 InvocationTargetException 410 { 411 return newInstance(getClassLoaders(spiClass), 412 new SPInterface(spiClass), 413 new PropertiesHolder(properties), 414 nullDefaultImpl); 415 } 416 417 /** 418 * Create new instance of class implementing SPI. 419 * 420 * @param spiClass Service Provider Interface Class. 421 * 422 * @param defaultImpl Default implementation. 423 * 424 * @return Instance of a class implementing the SPI. 425 * 426 * @exception DiscoveryException Thrown if the name of a class implementing 427 * the SPI cannot be found, if the class cannot be loaded and 428 * instantiated, or if the resulting class does not implement 429 * (or extend) the SPI. 430 */ 431 public Object newInstance(Class spiClass, String defaultImpl) 432 throws DiscoveryException, 433 InstantiationException, 434 IllegalAccessException, 435 NoSuchMethodException, 436 InvocationTargetException 437 { 438 return newInstance(getClassLoaders(spiClass), 439 new SPInterface(spiClass), 440 nullProperties, 441 new DefaultClassHolder(defaultImpl)); 442 } 443 444 /** 445 * Create new instance of class implementing SPI. 446 * 447 * @param spiClass Service Provider Interface Class. 448 * 449 * @param properties Used to determine name of SPI implementation, 450 * and passed to implementation.init() method if 451 * implementation implements Service interface. 452 * 453 * @param defaultImpl Default implementation. 454 * 455 * @return Instance of a class implementing the SPI. 456 * 457 * @exception DiscoveryException Thrown if the name of a class implementing 458 * the SPI cannot be found, if the class cannot be loaded and 459 * instantiated, or if the resulting class does not implement 460 * (or extend) the SPI. 461 */ 462 public Object newInstance(Class spiClass, Properties properties, String defaultImpl) 463 throws DiscoveryException, 464 InstantiationException, 465 IllegalAccessException, 466 NoSuchMethodException, 467 InvocationTargetException 468 { 469 return newInstance(getClassLoaders(spiClass), 470 new SPInterface(spiClass), 471 new PropertiesHolder(properties), 472 new DefaultClassHolder(defaultImpl)); 473 } 474 475 /** 476 * Create new instance of class implementing SPI. 477 * 478 * @param spiClass Service Provider Interface Class. 479 * 480 * @param propertiesFileName Used to determine name of SPI implementation, 481 * and passed to implementation.init() method if 482 * implementation implements Service interface. 483 * 484 * @param defaultImpl Default implementation. 485 * 486 * @return Instance of a class implementing the SPI. 487 * 488 * @exception DiscoveryException Thrown if the name of a class implementing 489 * the SPI cannot be found, if the class cannot be loaded and 490 * instantiated, or if the resulting class does not implement 491 * (or extend) the SPI. 492 */ 493 public Object newInstance(Class spiClass, String propertiesFileName, String defaultImpl) 494 throws DiscoveryException, 495 InstantiationException, 496 IllegalAccessException, 497 NoSuchMethodException, 498 InvocationTargetException 499 { 500 return newInstance(getClassLoaders(spiClass), 501 new SPInterface(spiClass), 502 new PropertiesHolder(propertiesFileName), 503 new DefaultClassHolder(defaultImpl)); 504 } 505 506 /** 507 * Create new instance of class implementing SPI. 508 * 509 * @param spi Service Provider Interface Class. 510 * 511 * @param properties Used to determine name of SPI implementation, 512 * and passed to implementation.init() method if 513 * implementation implements Service interface. 514 * 515 * @param defaultImpl Default implementation. 516 * 517 * @return Instance of a class implementing the SPI. 518 * 519 * @exception DiscoveryException Thrown if the name of a class implementing 520 * the SPI cannot be found, if the class cannot be loaded and 521 * instantiated, or if the resulting class does not implement 522 * (or extend) the SPI. 523 */ 524 public static Object newInstance(ClassLoaders loaders, 525 SPInterface spi, 526 PropertiesHolder properties, 527 DefaultClassHolder defaultImpl) 528 throws DiscoveryException, 529 InstantiationException, 530 IllegalAccessException, 531 NoSuchMethodException, 532 InvocationTargetException 533 { 534 return spi.newInstance(find(loaders, spi, properties, defaultImpl)); 535 } 536 537 /** 538 * <p>Discover names of SPI implementation Classes from properties. 539 * The names are the non-null values, in order, obtained from the following 540 * resources: 541 * <ul> 542 * <li>ManagedProperty.getProperty(SPI.class.getName());</li> 543 * <li>properties.getProperty(SPI.class.getName());</li> 544 * </ul> 545 * 546 * @param properties Properties that may define the implementation 547 * class name(s). 548 * 549 * @return String[] Name of classes implementing the SPI. 550 * 551 * @exception DiscoveryException Thrown if the name of a class implementing 552 * the SPI cannot be found. 553 */ 554 public static String[] discoverClassNames(SPInterface spi, 555 Properties properties) 556 { 557 Vector names = new Vector(); 558 559 String spiName = spi.getSPName(); 560 String propertyName = spi.getPropertyName(); 561 562 boolean includeAltProperty = !spiName.equals(propertyName); 563 564 // Try the (managed) system property spiName 565 String className = getManagedProperty(spiName); 566 if (className != null) names.addElement(className); 567 568 if (includeAltProperty) { 569 // Try the (managed) system property propertyName 570 className = getManagedProperty(propertyName); 571 if (className != null) names.addElement(className); 572 } 573 574 if (properties != null) { 575 // Try the properties parameter spiName 576 className = properties.getProperty(spiName); 577 if (className != null) names.addElement(className); 578 579 if (includeAltProperty) { 580 // Try the properties parameter propertyName 581 className = properties.getProperty(propertyName); 582 if (className != null) names.addElement(className); 583 } 584 } 585 586 String[] results = new String[names.size()]; 587 names.copyInto(results); 588 589 return results; 590 } 591 592 593 /** 594 * Load the class whose name is given by the value of a (Managed) 595 * System Property. 596 * 597 * @see ManagedProperties 598 * 599 * @param propertName the name of the system property whose value is 600 * the name of the class to load. 601 */ 602 public static String getManagedProperty(String propertyName) { 603 String value; 604 try { 605 value = ManagedProperties.getProperty(propertyName); 606 } catch (SecurityException e) { 607 value = null; 608 } 609 return value; 610 } 611 }