001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.discovery.tools;
018    
019    import java.lang.reflect.InvocationTargetException;
020    import java.util.Properties;
021    import java.util.Vector;
022    
023    import org.apache.commons.discovery.DiscoveryException;
024    import org.apache.commons.discovery.ResourceClass;
025    import org.apache.commons.discovery.ResourceClassIterator;
026    import org.apache.commons.discovery.ResourceNameIterator;
027    import org.apache.commons.discovery.resource.ClassLoaders;
028    import org.apache.commons.discovery.resource.classes.DiscoverClasses;
029    import org.apache.commons.discovery.resource.names.DiscoverServiceNames;
030    
031    
032    /**
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     * @author Richard A. Sitze
131     * @author Craig R. McClanahan
132     * @author Costin Manolache
133     * @version $Revision: 480374 $ $Date: 2006-11-29 04:33:25 +0100 (Mi, 29. 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    }