View Javadoc

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.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             // 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 }