1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.discovery.tools;
18
19 import java.lang.reflect.InvocationTargetException;
20 import java.util.Properties;
21 import java.util.Vector;
22
23 import org.apache.commons.discovery.DiscoveryException;
24 import org.apache.commons.discovery.ResourceClass;
25 import org.apache.commons.discovery.ResourceClassIterator;
26 import org.apache.commons.discovery.ResourceNameIterator;
27 import org.apache.commons.discovery.resource.ClassLoaders;
28 import org.apache.commons.discovery.resource.classes.DiscoverClasses;
29 import org.apache.commons.discovery.resource.names.DiscoverServiceNames;
30
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 * @author Richard A. Sitze
131 * @author Craig R. McClanahan
132 * @author Costin Manolache
133 * @version $Revision: 480374 $ $Date: 2006-11-28 19:33:25 -0800 (Tue, 28 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
328 if (classes.hasNext()) {
329 ResourceClass info = classes.nextResourceClass();
330 try {
331 return info.loadClass();
332 } catch (Exception e) {
333
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
349 while (classes.hasNext()) {
350 ResourceClass info = classes.nextResourceClass();
351 try {
352 return info.loadClass();
353 } catch (Exception e) {
354
355 }
356 }
357 }
358
359 throw new DiscoveryException("No implementation defined for " + spi.getSPName());
360
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
565 String className = getManagedProperty(spiName);
566 if (className != null) names.addElement(className);
567
568 if (includeAltProperty) {
569
570 className = getManagedProperty(propertyName);
571 if (className != null) names.addElement(className);
572 }
573
574 if (properties != null) {
575
576 className = properties.getProperty(spiName);
577 if (className != null) names.addElement(className);
578
579 if (includeAltProperty) {
580
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 }