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