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: * <p>Discover class that implements a given service interface,
033: * with discovery and configuration features similar to that employed
034: * by standard Java APIs such as JAXP.
035: * </p>
036: *
037: * <p>In the context of this package, a service interface is defined by a
038: * Service Provider Interface (SPI). The SPI is expressed as a Java interface,
039: * abstract class, or (base) class that defines an expected programming
040: * interface.
041: * </p>
042: *
043: * <p>DiscoverClass provides the <code>find</code> methods for locating a
044: * class that implements a service interface (SPI). Each form of
045: * <code>find</code> varies slightly, but they all perform the same basic
046: * function.
047: *
048: * The <code>DiscoverClass.find</code> methods proceed as follows:
049: * </p>
050: * <ul>
051: * <p><li>
052: * Get the name of an implementation class. The name is the first
053: * non-null value obtained from the following resources:
054: * <ul>
055: * <li>
056: * The value of the (scoped) system property whose name is the same as
057: * the SPI's fully qualified class name (as given by SPI.class.getName()).
058: * The <code>ScopedProperties</code> class provides a way to bind
059: * properties by classloader, in a secure hierarchy similar in concept
060: * to the way classloader find class and resource files.
061: * See <code>ScopedProperties</code> for more details.
062: * <p>If the ScopedProperties are not set by users, then behaviour
063: * is equivalent to <code>System.getProperty()</code>.
064: * </p>
065: * </li>
066: * <p><li>
067: * The value of a <code>Properties properties</code> property, if provided
068: * as a parameter, whose name is the same as the SPI's fully qualifed class
069: * name (as given by SPI.class.getName()).
070: * </li></p>
071: * <p><li>
072: * The value obtained using the JDK1.3+ 'Service Provider' specification
073: * (http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html) to locate a
074: * service named <code>SPI.class.getName()</code>. This is implemented
075: * internally, so there is not a dependency on JDK 1.3+.
076: * </li></p>
077: * </ul>
078: * </li></p>
079: * <p><li>
080: * If the name of the implementation class is non-null, load that class.
081: * The class loaded is the first class loaded by the following sequence
082: * of class loaders:
083: * <ul>
084: * <li>Thread Context Class Loader</li>
085: * <li>DiscoverSingleton's Caller's Class Loader</li>
086: * <li>SPI's Class Loader</li>
087: * <li>DiscoverSingleton's (this class or wrapper) Class Loader</li>
088: * <li>System Class Loader</li>
089: * </ul>
090: * An exception is thrown if the class cannot be loaded.
091: * </li></p>
092: * <p><li>
093: * If the name of the implementation class is null, AND the default
094: * implementation class name (<code>defaultImpl</code>) is null,
095: * then an exception is thrown.
096: * </li></p>
097: * <p><li>
098: * If the name of the implementation class is null, AND the default
099: * implementation class (<code>defaultImpl</code>) is non-null,
100: * then load the default implementation class. The class loaded is the
101: * first class loaded by the following sequence of class loaders:
102: * <ul>
103: * <li>SPI's Class Loader</li>
104: * <li>DiscoverSingleton's (this class or wrapper) Class Loader</li>
105: * <li>System Class Loader</li>
106: * </ul>
107: * <p>
108: * This limits the scope in which the default class loader can be found
109: * to the SPI, DiscoverSingleton, and System class loaders. The assumption here
110: * is that the default implementation is closely associated with the SPI
111: * or system, and is not defined in the user's application space.
112: * </p>
113: * <p>
114: * An exception is thrown if the class cannot be loaded.
115: * </p>
116: * </li></p>
117: * <p><li>
118: * Verify that the loaded class implements the SPI: an exception is thrown
119: * if the loaded class does not implement the SPI.
120: * </li></p>
121: * </ul>
122: * </p>
123: *
124: * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is modelled
125: * after the SAXParserFactory and DocumentBuilderFactory implementations
126: * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.
127: * </p>
128: *
129: * @author Richard A. Sitze
130: * @author Craig R. McClanahan
131: * @author Costin Manolache
132: * @version $Revision: 480374 $ $Date: 2006-11-28 19:33:25 -0800 (Tue, 28 Nov 2006) $
133: */
134: public class DiscoverClass {
135: /**
136: * Readable placeholder for a null value.
137: */
138: public static final DefaultClassHolder nullDefaultImpl = null;
139:
140: /**
141: * Readable placeholder for a null value.
142: */
143: public static final PropertiesHolder nullProperties = null;
144:
145: private ClassLoaders classLoaders = null;
146:
147: /**
148: * Create a class instance with dynamic environment
149: * (thread context class loader is determined on each call).
150: *
151: * Dynamically construct class loaders on each call.
152: */
153: public DiscoverClass() {
154: this (null);
155: }
156:
157: /**
158: * Create a class instance with dynamic environment
159: * (thread context class loader is determined on each call).
160: *
161: * Cache static list of class loaders for each call.
162: */
163: public DiscoverClass(ClassLoaders classLoaders) {
164: this .classLoaders = classLoaders;
165: }
166:
167: public ClassLoaders getClassLoaders(Class spiClass) {
168: return classLoaders;
169: }
170:
171: /**
172: * Find class implementing SPI.
173: *
174: * @param spiClass Service Provider Interface Class.
175: *
176: * @return Class implementing the SPI.
177: *
178: * @exception DiscoveryException Thrown if the name of a class implementing
179: * the SPI cannot be found, if the class cannot be loaded, or if
180: * the resulting class does not implement (or extend) the SPI.
181: */
182: public Class find(Class spiClass) throws DiscoveryException {
183: return find(getClassLoaders(spiClass),
184: new SPInterface(spiClass), nullProperties,
185: nullDefaultImpl);
186: }
187:
188: /**
189: * Find class implementing SPI.
190: *
191: * @param spiClass Service Provider Interface Class.
192: *
193: * @param properties Used to determine name of SPI implementation.
194: *
195: * @return Class implementing the SPI.
196: *
197: * @exception DiscoveryException Thrown if the name of a class implementing
198: * the SPI cannot be found, if the class cannot be loaded, or if
199: * the resulting class does not implement (or extend) the SPI.
200: */
201: public Class find(Class spiClass, Properties properties)
202: throws DiscoveryException {
203: return find(getClassLoaders(spiClass),
204: new SPInterface(spiClass), new PropertiesHolder(
205: properties), nullDefaultImpl);
206: }
207:
208: /**
209: * Find class implementing SPI.
210: *
211: * @param spiClass Service Provider Interface Class.
212: *
213: * @param defaultImpl Default implementation name.
214: *
215: * @return Class implementing the SPI.
216: *
217: * @exception DiscoveryException Thrown if the name of a class implementing
218: * the SPI cannot be found, if the class cannot be loaded, or if
219: * the resulting class does not implement (or extend) the SPI.
220: */
221: public Class find(Class spiClass, String defaultImpl)
222: throws DiscoveryException {
223: return find(getClassLoaders(spiClass),
224: new SPInterface(spiClass), nullProperties,
225: new DefaultClassHolder(defaultImpl));
226: }
227:
228: /**
229: * Find class implementing SPI.
230: *
231: * @param spiClass Service Provider Interface Class.
232: *
233: * @param properties Used to determine name of SPI implementation,.
234: *
235: * @param defaultImpl Default implementation class.
236: *
237: * @return Class implementing the SPI.
238: *
239: * @exception DiscoveryException Thrown if the name of a class implementing
240: * the SPI cannot be found, if the class cannot be loaded, or if
241: * the resulting class does not implement (or extend) the SPI.
242: */
243: public Class find(Class spiClass, Properties properties,
244: String defaultImpl) throws DiscoveryException {
245: return find(getClassLoaders(spiClass),
246: new SPInterface(spiClass), new PropertiesHolder(
247: properties),
248: new DefaultClassHolder(defaultImpl));
249: }
250:
251: /**
252: * Find class implementing SPI.
253: *
254: * @param spiClass Service Provider Interface Class.
255: *
256: * @param propertiesFileName Used to determine name of SPI implementation,.
257: *
258: * @param defaultImpl Default implementation class.
259: *
260: * @return Class implementing the SPI.
261: *
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 Class find(Class spiClass, String propertiesFileName,
267: String defaultImpl) throws DiscoveryException {
268: return find(getClassLoaders(spiClass),
269: new SPInterface(spiClass), new PropertiesHolder(
270: propertiesFileName), new DefaultClassHolder(
271: defaultImpl));
272: }
273:
274: /**
275: * Find class implementing SPI.
276: *
277: * @param spi Service Provider Interface Class.
278: *
279: * @param properties Used to determine name of SPI implementation,.
280: *
281: * @param defaultImpl Default implementation class.
282: *
283: * @return Class implementing the SPI.
284: *
285: * @exception DiscoveryException Thrown if the name of a class implementing
286: * the SPI cannot be found, if the class cannot be loaded, or if
287: * the resulting class does not implement (or extend) the SPI.
288: */
289: public static Class find(ClassLoaders loaders, SPInterface spi,
290: PropertiesHolder properties, DefaultClassHolder defaultImpl)
291: throws DiscoveryException {
292: if (loaders == null) {
293: loaders = ClassLoaders.getLibLoaders(spi.getSPClass(),
294: DiscoverClass.class, true);
295: }
296:
297: Properties props = (properties == null) ? null : properties
298: .getProperties(spi, loaders);
299:
300: String[] classNames = discoverClassNames(spi, props);
301:
302: if (classNames.length > 0) {
303: DiscoverClasses classDiscovery = new DiscoverClasses(
304: loaders);
305:
306: ResourceClassIterator classes = classDiscovery
307: .findResourceClasses(classNames[0]);
308:
309: // If it's set as a property.. it had better be there!
310: if (classes.hasNext()) {
311: ResourceClass info = classes.nextResourceClass();
312: try {
313: return info.loadClass();
314: } catch (Exception e) {
315: // ignore
316: }
317: }
318: } else {
319: ResourceNameIterator classIter = (new DiscoverServiceNames(
320: loaders)).findResourceNames(spi.getSPName());
321:
322: ResourceClassIterator classes = (new DiscoverClasses(
323: loaders)).findResourceClasses(classIter);
324:
325: if (!classes.hasNext() && defaultImpl != null) {
326: return defaultImpl.getDefaultClass(spi, loaders);
327: }
328:
329: // Services we iterate through until we find one that loads..
330: while (classes.hasNext()) {
331: ResourceClass info = classes.nextResourceClass();
332: try {
333: return info.loadClass();
334: } catch (Exception e) {
335: // ignore
336: }
337: }
338: }
339:
340: throw new DiscoveryException("No implementation defined for "
341: + spi.getSPName());
342: // return null;
343: }
344:
345: /**
346: * Create new instance of class implementing SPI.
347: *
348: * @param spiClass Service Provider Interface Class.
349: *
350: * @return Instance of a class implementing the SPI.
351: *
352: * @exception DiscoveryException Thrown if the name of a class implementing
353: * the SPI cannot be found, if the class cannot be loaded and
354: * instantiated, or if the resulting class does not implement
355: * (or extend) the SPI.
356: */
357: public Object newInstance(Class spiClass)
358: throws DiscoveryException, InstantiationException,
359: IllegalAccessException, NoSuchMethodException,
360: InvocationTargetException {
361: return newInstance(getClassLoaders(spiClass), new SPInterface(
362: spiClass), nullProperties, nullDefaultImpl);
363: }
364:
365: /**
366: * Create new instance of class implementing SPI.
367: *
368: * @param spiClass Service Provider Interface Class.
369: *
370: * @param properties Used to determine name of SPI implementation,
371: * and passed to implementation.init() method if
372: * implementation implements Service interface.
373: *
374: * @return Instance of a class implementing the SPI.
375: *
376: * @exception DiscoveryException Thrown if the name of a class implementing
377: * the SPI cannot be found, if the class cannot be loaded and
378: * instantiated, or if the resulting class does not implement
379: * (or extend) the SPI.
380: */
381: public Object newInstance(Class spiClass, Properties properties)
382: throws DiscoveryException, InstantiationException,
383: IllegalAccessException, NoSuchMethodException,
384: InvocationTargetException {
385: return newInstance(getClassLoaders(spiClass), new SPInterface(
386: spiClass), new PropertiesHolder(properties),
387: nullDefaultImpl);
388: }
389:
390: /**
391: * Create new instance of class implementing SPI.
392: *
393: * @param spiClass Service Provider Interface Class.
394: *
395: * @param defaultImpl Default implementation.
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, String defaultImpl)
405: throws DiscoveryException, InstantiationException,
406: IllegalAccessException, NoSuchMethodException,
407: InvocationTargetException {
408: return newInstance(getClassLoaders(spiClass), new SPInterface(
409: spiClass), nullProperties, new DefaultClassHolder(
410: defaultImpl));
411: }
412:
413: /**
414: * Create new instance of class implementing SPI.
415: *
416: * @param spiClass Service Provider Interface Class.
417: *
418: * @param properties Used to determine name of SPI implementation,
419: * and passed to implementation.init() method if
420: * implementation implements Service interface.
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, Properties properties,
432: String defaultImpl) throws DiscoveryException,
433: InstantiationException, IllegalAccessException,
434: NoSuchMethodException, InvocationTargetException {
435: return newInstance(getClassLoaders(spiClass), new SPInterface(
436: spiClass), new PropertiesHolder(properties),
437: new DefaultClassHolder(defaultImpl));
438: }
439:
440: /**
441: * Create new instance of class implementing SPI.
442: *
443: * @param spiClass Service Provider Interface Class.
444: *
445: * @param propertiesFileName Used to determine name of SPI implementation,
446: * and passed to implementation.init() method if
447: * implementation implements Service interface.
448: *
449: * @param defaultImpl Default implementation.
450: *
451: * @return Instance of a class implementing the SPI.
452: *
453: * @exception DiscoveryException Thrown if the name of a class implementing
454: * the SPI cannot be found, if the class cannot be loaded and
455: * instantiated, or if the resulting class does not implement
456: * (or extend) the SPI.
457: */
458: public Object newInstance(Class spiClass,
459: String propertiesFileName, String defaultImpl)
460: throws DiscoveryException, InstantiationException,
461: IllegalAccessException, NoSuchMethodException,
462: InvocationTargetException {
463: return newInstance(getClassLoaders(spiClass), new SPInterface(
464: spiClass), new PropertiesHolder(propertiesFileName),
465: new DefaultClassHolder(defaultImpl));
466: }
467:
468: /**
469: * Create new instance of class implementing SPI.
470: *
471: * @param spi Service Provider Interface Class.
472: *
473: * @param properties Used to determine name of SPI implementation,
474: * and passed to implementation.init() method if
475: * implementation implements Service interface.
476: *
477: * @param defaultImpl Default implementation.
478: *
479: * @return Instance of a class implementing the SPI.
480: *
481: * @exception DiscoveryException Thrown if the name of a class implementing
482: * the SPI cannot be found, if the class cannot be loaded and
483: * instantiated, or if the resulting class does not implement
484: * (or extend) the SPI.
485: */
486: public static Object newInstance(ClassLoaders loaders,
487: SPInterface spi, PropertiesHolder properties,
488: DefaultClassHolder defaultImpl) throws DiscoveryException,
489: InstantiationException, IllegalAccessException,
490: NoSuchMethodException, InvocationTargetException {
491: return spi.newInstance(find(loaders, spi, properties,
492: defaultImpl));
493: }
494:
495: /**
496: * <p>Discover names of SPI implementation Classes from properties.
497: * The names are the non-null values, in order, obtained from the following
498: * resources:
499: * <ul>
500: * <li>ManagedProperty.getProperty(SPI.class.getName());</li>
501: * <li>properties.getProperty(SPI.class.getName());</li>
502: * </ul>
503: *
504: * @param properties Properties that may define the implementation
505: * class name(s).
506: *
507: * @return String[] Name of classes implementing the SPI.
508: *
509: * @exception DiscoveryException Thrown if the name of a class implementing
510: * the SPI cannot be found.
511: */
512: public static String[] discoverClassNames(SPInterface spi,
513: Properties properties) {
514: Vector names = new Vector();
515:
516: String spiName = spi.getSPName();
517: String propertyName = spi.getPropertyName();
518:
519: boolean includeAltProperty = !spiName.equals(propertyName);
520:
521: // Try the (managed) system property spiName
522: String className = getManagedProperty(spiName);
523: if (className != null)
524: names.addElement(className);
525:
526: if (includeAltProperty) {
527: // Try the (managed) system property propertyName
528: className = getManagedProperty(propertyName);
529: if (className != null)
530: names.addElement(className);
531: }
532:
533: if (properties != null) {
534: // Try the properties parameter spiName
535: className = properties.getProperty(spiName);
536: if (className != null)
537: names.addElement(className);
538:
539: if (includeAltProperty) {
540: // Try the properties parameter propertyName
541: className = properties.getProperty(propertyName);
542: if (className != null)
543: names.addElement(className);
544: }
545: }
546:
547: String[] results = new String[names.size()];
548: names.copyInto(results);
549:
550: return results;
551: }
552:
553: /**
554: * Load the class whose name is given by the value of a (Managed)
555: * System Property.
556: *
557: * @see ManagedProperties
558: *
559: * @param propertName the name of the system property whose value is
560: * the name of the class to load.
561: */
562: public static String getManagedProperty(String propertyName) {
563: String value;
564: try {
565: value = ManagedProperties.getProperty(propertyName);
566: } catch (SecurityException e) {
567: value = null;
568: }
569: return value;
570: }
571: }
|