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 }