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 }