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 */
017package org.apache.commons.discovery.tools;
018
019import java.lang.reflect.InvocationTargetException;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Properties;
023
024import org.apache.commons.discovery.DiscoveryException;
025import org.apache.commons.discovery.ResourceClass;
026import org.apache.commons.discovery.ResourceClassIterator;
027import org.apache.commons.discovery.ResourceNameIterator;
028import org.apache.commons.discovery.resource.ClassLoaders;
029import org.apache.commons.discovery.resource.classes.DiscoverClasses;
030import org.apache.commons.discovery.resource.names.DiscoverServiceNames;
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 * @version $Revision: 1090010 $ $Date: 2011-04-07 23:05:58 +0200 (Thu, 07 Apr 2011) $
131 */
132public 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}