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.LinkedList;
021    import java.util.List;
022    import java.util.Properties;
023    
024    import org.apache.commons.discovery.DiscoveryException;
025    import org.apache.commons.discovery.ResourceClass;
026    import org.apache.commons.discovery.ResourceClassIterator;
027    import org.apache.commons.discovery.ResourceNameIterator;
028    import org.apache.commons.discovery.resource.ClassLoaders;
029    import org.apache.commons.discovery.resource.classes.DiscoverClasses;
030    import 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     */
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    }