1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.discovery.tools;
18
19 import java.lang.reflect.InvocationTargetException;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Properties;
23
24 import org.apache.commons.discovery.DiscoveryException;
25 import org.apache.commons.discovery.ResourceClass;
26 import org.apache.commons.discovery.ResourceClassIterator;
27 import org.apache.commons.discovery.ResourceNameIterator;
28 import org.apache.commons.discovery.resource.ClassLoaders;
29 import org.apache.commons.discovery.resource.classes.DiscoverClasses;
30 import org.apache.commons.discovery.resource.names.DiscoverServiceNames;
31
32 /**
33 * <p>Discover class that implements a given service interface,
34 * with discovery and configuration features similar to that employed
35 * by standard Java APIs such as JAXP.
36 * </p>
37 *
38 * <p>In the context of this package, a service interface is defined by a
39 * Service Provider Interface (SPI). The SPI is expressed as a Java interface,
40 * abstract class, or (base) class that defines an expected programming
41 * interface.
42 * </p>
43 *
44 * <p>DiscoverClass provides the <code>find</code> methods for locating a
45 * class that implements a service interface (SPI). Each form of
46 * <code>find</code> varies slightly, but they all perform the same basic
47 * function.
48 *
49 * The <code>DiscoverClass.find</code> methods proceed as follows:
50 * </p>
51 * <ul>
52 * <p><li>
53 * Get the name of an implementation class. The name is the first
54 * non-null value obtained from the following resources:
55 * <ul>
56 * <li>
57 * The value of the (scoped) system property whose name is the same as
58 * the SPI's fully qualified class name (as given by SPI.class.getName()).
59 * The <code>ScopedProperties</code> class provides a way to bind
60 * properties by classloader, in a secure hierarchy similar in concept
61 * to the way classloader find class and resource files.
62 * See <code>ScopedProperties</code> for more details.
63 * <p>If the ScopedProperties are not set by users, then behaviour
64 * is equivalent to <code>System.getProperty()</code>.
65 * </p>
66 * </li>
67 * <p><li>
68 * The value of a <code>Properties properties</code> property, if provided
69 * as a parameter, whose name is the same as the SPI's fully qualifed class
70 * name (as given by SPI.class.getName()).
71 * </li></p>
72 * <p><li>
73 * The value obtained using the JDK1.3+ 'Service Provider' specification
74 * (http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html) to locate a
75 * service named <code>SPI.class.getName()</code>. This is implemented
76 * internally, so there is not a dependency on JDK 1.3+.
77 * </li></p>
78 * </ul>
79 * </li></p>
80 * <p><li>
81 * If the name of the implementation class is non-null, load that class.
82 * The class loaded is the first class loaded by the following sequence
83 * of class loaders:
84 * <ul>
85 * <li>Thread Context Class Loader</li>
86 * <li>DiscoverSingleton's Caller's Class Loader</li>
87 * <li>SPI's Class Loader</li>
88 * <li>DiscoverSingleton's (this class or wrapper) Class Loader</li>
89 * <li>System Class Loader</li>
90 * </ul>
91 * An exception is thrown if the class cannot be loaded.
92 * </li></p>
93 * <p><li>
94 * If the name of the implementation class is null, AND the default
95 * implementation class name (<code>defaultImpl</code>) is null,
96 * then an exception is thrown.
97 * </li></p>
98 * <p><li>
99 * 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 }