001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.geronimo.osgi.locator;
020    
021    import java.io.BufferedReader;
022    import java.io.File;
023    import java.io.FileInputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.InputStreamReader;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.Enumeration;
031    import java.util.LinkedHashSet;
032    import java.util.List;
033    import java.util.Properties;
034    import java.util.Set;
035    
036    import org.apache.geronimo.osgi.registry.api.ProviderRegistry;
037    import org.osgi.framework.Bundle;
038    import org.osgi.framework.BundleContext;
039    import org.osgi.util.tracker.ServiceTracker;
040    
041    public class ProviderLocator {
042        // our bundle context
043        static private BundleContext context;
044        // a service tracker for the registry service
045        // NB:  This is declared as just Object to avoid classloading issues if we're running
046        // outside of an OSGi environment.
047        static private Object registryTracker;
048    
049        private ProviderLocator() {
050            // private constructor to prevent an instance from getting created.
051        }
052    
053        /**
054         * initialize the tracker statics for this bundle
055         *
056         * @param c      The starup BundleContext.
057         */
058        public static void init(BundleContext c) {
059            try {
060                // just create a tracker for our lookup service
061                // NB:  We use the hard coded name in case the registry service has not
062                // been started first.  The ServiceTracker itself only uses the string name.
063                // We need to avoid trying to load the ProviderRegistry interface until the
064                // registry tracker returns a non-null service instance.
065                registryTracker = new ServiceTracker(c, "org.apache.geronimo.osgi.registry.api.ProviderRegistry", null);
066                ((ServiceTracker)registryTracker).open();
067                // do this last...it helps indicate if we have an initialized registry.
068                context = c;
069            } catch (Throwable e) {
070                // if there were any errors, then the registry is not available.
071                registryTracker = null;
072            }
073        }
074    
075    
076        /**
077         * Cleanup resources on bundle shutdown.
078         */
079        public static void destroy() {
080            if (registryTracker != null) {
081                // shutdown our tracking of the provider registry.
082                ((ServiceTracker)registryTracker).close();
083                registryTracker = null;
084            }
085        }
086    
087    
088        /**
089         * Locate a class by its provider id indicator. .
090         *
091         * @param providerId The provider id (generally, a fully qualified class name).
092         *
093         * @return The Class corresponding to this provider id.  Returns null
094         *         if this is not registered or the indicated class can't be
095         *         loaded.
096         */
097        static public Class<?> locate(String providerId) {
098            Object registry = getRegistry();
099            // if no registry service available, this is a failure
100            if (registry == null) {
101                return null;
102            }
103            // get the service, if it exists.  NB, if there is a service object,
104            // then the extender and the interface class are available, so this cast should be
105            // safe now.
106    
107            // the rest of the work is done by the registry
108            return ((ProviderRegistry)registry).locate(providerId);
109        }
110    
111        /**
112         * Locate all class files that match a given factory id.
113         *
114         * @param providerId The target provider identifier.
115         *
116         * @return A List containing the class objects corresponding to the
117         *         provider identifier.  Returns an empty list if no
118         *         matching classes can be located.
119         */
120        static public List<Class<?>> locateAll(String providerId) {
121            Object registry = getRegistry();
122    
123            // if no registry service available, this is a failure
124            if (registry == null) {
125                return new ArrayList<Class<?>>();
126            }
127            // get the service, if it exists.  NB, if there is a service object,
128            // then the extender and the interface class are available, so this cast should be
129            // safe now.
130    
131            // the rest of the work is done by the registry
132            return ((ProviderRegistry)registry).locateAll(providerId);
133        }
134    
135        /**
136         * Utility class for locating a class with OSGi registry
137         * support.  Uses the thread context classloader as part of
138         * the search order.
139         *
140         * @param className The name of the target class.
141         *
142         * @return The loaded class.
143         * @exception ClassNotFoundException
144         *                   Thrown if the class cannot be located.
145         */
146        static public Class<?> loadClass(String className) throws ClassNotFoundException {
147            return loadClass(className, null, Thread.currentThread().getContextClassLoader());
148        }
149    
150        /**
151         * Utility class for locating a class with OSGi registry
152         * support.  Uses the thread context classloader as part of
153         * the search order.
154         *
155         * @param className The name of the target class.
156         *
157         * @return The loaded class.
158         * @exception ClassNotFoundException
159         *                   Thrown if the class cannot be located.
160         */
161        static public Class<?> loadClass(String className, Class<?> contextClass) throws ClassNotFoundException {
162            return loadClass(className, contextClass, Thread.currentThread().getContextClassLoader());
163        }
164    
165        /**
166         * Standardized utility method for performing class lookups
167         * with support for OSGi registry lookups.
168         *
169         * @param className The name of the target class.
170         * @param loader    An optional class loader.
171         *
172         * @return The loaded class
173         * @exception ClassNotFoundException
174         *                   Thrown if the class cannot be loaded.
175         */
176        static public Class<?> loadClass(String className, Class<?>contextClass, ClassLoader loader) throws ClassNotFoundException {
177            // ideally, this should be last.  However, some of the bundles duplicate classes
178            // found on the boot delegation, so we need to check this first to keep
179            // from picking up one of the default implementations.
180            Class cls = locate(className);
181            if (cls != null) {
182                return cls;
183            }
184    
185            if (loader != null) {
186                try {
187                    return loader.loadClass(className);
188                } catch (ClassNotFoundException x) {
189                }
190            }
191            if (contextClass != null) {
192                loader = contextClass.getClassLoader();
193            }
194            // try again using the class context loader
195            return Class.forName(className, true, loader);
196        }
197    
198    
199        /**
200         * Get a single service instance that matches an interface
201         * definition.
202         *
203         * @param iface  The name of the required interface.
204         * @param contextClass
205         *               The class requesting the lookup (used for class resolution).
206         * @param loader A class loader to use for searching for service definitions
207         *               and loading classes.
208         *
209         * @return The service instance, or null if no matching services
210         *         can be found.
211         * @exception Exception Thrown for any classloading or exceptions thrown
212         *                      trying to instantiate a service instance.
213         */
214        static public Object getService(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
215            // if we are working in an OSGi environment, then process the service
216            // registry first.  Ideally, we would do this last, but because of boot delegation
217            // issues with some API implementations, we must try the OSGi version first
218            Object registry = getRegistry();
219            if (registry != null) {
220                // get the service, if it exists.  NB, if there is a service object,
221                // then the extender and the interface class are available, so this cast should be
222                // safe now.
223                // the rest of the work is done by the registry
224                Object service = ((ProviderRegistry)registry).getService(iface);
225                if (service != null) {
226                    return service;
227                }
228            }
229    
230            // try for a classpath locatable instance next.  If we find an appropriate class mapping,
231            // create an instance and return it.
232            Class<?> cls = locateServiceClass(iface, contextClass, loader);
233            if (cls != null) {
234                return cls.newInstance();
235            }
236            // a provider was not found
237            return null;
238        }
239    
240    
241        /**
242         * Locate a service class that matches an interface
243         * definition.
244         *
245         * @param iface  The name of the required interface.
246         * @param contextClass
247         *               The class requesting the lookup (used for class resolution).
248         * @param loader A class loader to use for searching for service definitions
249         *               and loading classes.
250         *
251         * @return The located class, or null if no matching services
252         *         can be found.
253         * @exception Exception Thrown for any classloading exceptions thrown
254         *                      trying to load the class.
255         */
256        static public Class<?> getServiceClass(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
257            // if we are working in an OSGi environment, then process the service
258            // registry first.  Ideally, we would do this last, but because of boot delegation
259            // issues with some API implementations, we must try the OSGi version first
260            Object registry = getRegistry();
261            if (registry != null) {
262                // get the service, if it exists.  NB, if there is a service object,
263                // then the extender and the interface class are available, so this cast should be
264                // safe now.
265    
266                // If we've located stuff in the registry, then return it
267                Class<?> cls = ((ProviderRegistry)registry).getServiceClass(iface);
268                if (cls != null) {
269                    return cls;
270                }
271            }
272    
273            // try for a classpath locatable instance first.  If we find an appropriate class mapping,
274            // create an instance and return it.
275            return locateServiceClass(iface, contextClass, loader);
276        }
277    
278    
279        /**
280         * Get a list of services that match a given interface
281         * name.  This searches both the current class path and
282         * the global repository for matches.
283         *
284         * @param iface  The name of the required interface.
285         * @param contextClass
286         *               The class requesting the lookup (used for class resolution).
287         * @param loader A class loader to use for searching for service definitions
288         *               and loading classes.
289         *
290         * @return A list of matching services.  Returns an empty list if there
291         *         are no matches.
292         * @exception Exception Thrown for any classloading or exceptions thrown
293         *                      trying to instantiate a service instance.
294         */
295        static public List<Object> getServices(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
296            List<Object> services = new ArrayList<Object>();
297    
298            // because of boot delegation issues with some of the API implementations, it is necessary
299            // to process the OSGi registered versions first to allow override of JRE provided APIs.
300            Object registry = getRegistry();
301            if (registry != null) {
302                // get the service, if it exists.  NB, if there is a service object,
303                // then the extender and the interface class are available, so this cast should be
304                // safe now.
305                // get any registered service instances now
306                List<Object> globalServices = ((ProviderRegistry)registry).getServices(iface);
307                // add to our list also
308                if (globalServices != null) {
309                    services.addAll(globalServices);
310                }
311            }
312    
313            // try for a classpath locatable instance second.  If we find an appropriate class mapping,
314            // create an instance and return it.
315            Collection<Class<?>> classes = locateServiceClasses(iface, contextClass, loader);
316            if (classes != null) {
317                // create an instance of each of these classes
318                for (Class<?> cls : classes) {
319                    services.add(cls.newInstance());
320                }
321            }
322    
323            // now return the merged set
324            return services;
325        }
326    
327    
328        /**
329         * Get a list of service class implementations that match
330         * an interface name.  This searches both the current class path and
331         * the global repository for matches.
332         *
333         * @param iface  The name of the required interface.
334         * @param contextClass
335         *               The class requesting the lookup (used for class resolution).
336         * @param loader A class loader to use for searching for service definitions
337         *               and loading classes.
338         *
339         * @return A list of matching provider classes.  Returns an empty list if there
340         *         are no matches.
341         * @exception Exception Thrown for any classloading exceptions thrown
342         *                      trying to load a provider class.
343         */
344        static public List<Class<?>> getServiceClasses(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
345            Set<Class<?>> serviceClasses = new LinkedHashSet<Class<?>>();
346    
347            // because of boot delegation issues with some of the API implementations, it is necessary
348            // to process the OSGi registered versions first to allow override of JRE provided APIs.
349            Object registry = getRegistry();
350            if (registry != null) {
351                // get the service, if it exists.  NB, if there is a service object,
352                // then the extender and the interface class are available, so this cast should be
353                // safe now.
354                // get any registered service provider classes now
355                List<Class<?>> globalServices = ((ProviderRegistry)registry).getServiceClasses(iface);
356                // add to our list also
357                if (globalServices != null) {
358                    serviceClasses.addAll(globalServices);
359                }
360            }
361    
362            // try for a classpath locatable classes second.  If we find an appropriate class mapping,
363            // add this to our return collection.
364            Collection<Class<?>> classes = locateServiceClasses(iface, contextClass, loader);
365            if (classes != null) {
366                serviceClasses.addAll(classes);
367            }
368            // now return the merged set
369            return new ArrayList(serviceClasses);
370        }
371    
372    
373        /**
374         * Locate the first class name for a META-INF/services definition
375         * of a given class.  The first matching provider is
376         * returned.
377         *
378         * @param iface  The interface class name used for the match.
379         * @param loader The classloader for locating resources.
380         *
381         * @return The mapped provider name, if found.  Returns null if
382         *         no mapping is located.
383         */
384        static private String locateServiceClassName(String iface, Class<?> contextClass, ClassLoader loader) {
385            // search first with the loader class path
386            String name = locateServiceClassName(iface, loader);
387            if (name != null) {
388                return name;
389            }
390            // then with the context class, if there is one
391            if (contextClass != null) {
392                name = locateServiceClassName(iface, contextClass.getClassLoader());
393                if (name != null) {
394                    return name;
395                }
396            }
397            // not found
398            return null;
399        }
400    
401    
402        /**
403         * Locate a classpath-define service mapping.
404         *
405         * @param iface  The required interface name.
406         * @param loader The ClassLoader instance to use to locate the service.
407         *
408         * @return The mapped class name, if one is found.  Returns null if the
409         *         mapping is not located.
410         */
411        static private String locateServiceClassName(String iface, ClassLoader loader) {
412            if (loader != null) {
413                try {
414                    // we only look at resources that match the file name, using the specified loader
415                    String service = "META-INF/services/" + iface;
416                    Enumeration<URL> providers = loader.getResources(service);
417    
418                    while (providers.hasMoreElements()) {
419                        List<String>providerNames = parseServiceDefinition(providers.nextElement());
420                        // if there is something defined here, return the first entry
421                        if (!providerNames.isEmpty()) {
422                            return providerNames.get(0);
423                        }
424                    }
425                } catch (IOException e) {
426                }
427            }
428            // not found
429            return null;
430        }
431    
432    
433        /**
434         * Locate the first class for a META-INF/services definition
435         * of a given interface class.  The first matching provider is
436         * returned.
437         *
438         * @param iface  The interface class name used for the match.
439         * @param loader The classloader for locating resources.
440         *
441         * @return The mapped provider class, if found.  Returns null if
442         *         no mapping is located.
443         */
444        static private Class<?> locateServiceClass(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
445            String className = locateServiceClassName(iface, contextClass, loader);
446            if (className == null) {
447                return null;
448            }
449    
450            // we found a name, try loading the class.  This will throw an exception if there is an error
451            return loadClass(className, contextClass, loader);
452        }
453    
454    
455        /**
456         * Locate all class names name for a META-INF/services definition
457         * of a given class.
458         *
459         * @param iface  The interface class name used for the match.
460         * @param loader The classloader for locating resources.
461         *
462         * @return The mapped provider name, if found.  Returns null if
463         *         no mapping is located.
464         */
465        static private Collection<String> locateServiceClassNames(String iface, Class<?> contextClass, ClassLoader loader) {
466            Set<String> names = new LinkedHashSet<String>();
467    
468            locateServiceClassNames(iface, loader, names);
469            if (contextClass != null) {
470                locateServiceClassNames(iface, contextClass.getClassLoader(), names);
471            }
472    
473            return names;
474        }
475    
476    
477        /**
478         * Locate all class names name for a META-INF/services definition
479         * of a given class.
480         *
481         * @param iface  The interface class name used for the match.
482         * @param loader The classloader for locating resources.
483         *
484         * @return The mapped provider name, if found.  Returns null if
485         *         no mapping is located.
486         */
487        static void locateServiceClassNames(String iface, ClassLoader loader, Set names) {
488            if (loader != null) {
489    
490                try {
491                    // we only look at resources that match the file name, using the specified loader
492                    String service = "META-INF/services/" + iface;
493                    Enumeration<URL> providers = loader.getResources(service);
494    
495                    while (providers.hasMoreElements()) {
496                        List<String>providerNames = parseServiceDefinition(providers.nextElement());
497                        // just add all of these to the list
498                        names.addAll(providerNames);
499                    }
500                } catch (IOException e) {
501                }
502            }
503        }
504    
505    
506        /**
507         * Locate all classes that map to a given provider class definition.  This will
508         * search both the services directories, as well as the provider classes from the
509         * OSGi provider registry.
510         *
511         * @param iface  The interface class name used for the match.
512         * @param loader The classloader for locating resources.
513         *
514         * @return A list of all mapped classes, if found.  Returns an empty list if
515         *         no mappings are found.
516         */
517        static private Collection<Class<?>> locateServiceClasses(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
518            // get the set of names from services definitions on the classpath
519            Collection<String> classNames = locateServiceClassNames(iface, contextClass, loader);
520            Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
521    
522            // load each class and add to our return set
523            for (String name : classNames) {
524                classes.add(loadClass(name, contextClass, loader));
525            }
526            return classes;
527        }
528    
529    
530        /**
531         * Parse a definition file and return the names of all included implementation classes
532         * contained within the file.
533         *
534         * @param u      The URL of the file
535         *
536         * @return A list of all matching classes.  Returns an empty list
537         *         if no matches are found.
538         */
539        static private List<String> parseServiceDefinition(URL u) {
540            final String url = u.toString();
541            List<String> classes = new ArrayList<String>();
542            // ignore directories
543            if (url.endsWith("/")) {
544                return classes;
545            }
546            // the identifier used for the provider is the last item in the URL.
547            final String providerId = url.substring(url.lastIndexOf("/") + 1);
548            try {
549                BufferedReader br = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8"));
550                // the file can be multiple lines long, with comments.  A single file can define multiple providers
551                // for a single key, so we might need to create multiple entries.  If the file does not contain any
552                // definition lines, then as a default, we use the providerId as an implementation class also.
553                String line = br.readLine();
554                while (line != null) {
555                    // we allow comments on these lines, and a line can be all comment
556                    int comment = line.indexOf('#');
557                    if (comment != -1) {
558                        line = line.substring(0, comment);
559                    }
560                    line = line.trim();
561                    // if there is nothing left on the line after stripping white space and comments, skip this
562                    if (line.length() > 0) {
563                        // add this to our list
564                        classes.add(line);
565                    }
566                    // keep reading until the end.
567                    line = br.readLine();
568                }
569                br.close();
570            } catch (IOException e) {
571                // ignore errors and handle as default
572            }
573            return classes;
574        }
575    
576        /**
577         * Perform a service class discovery by looking for a
578         * property in a target properties file located in the
579         * java.home directory.
580         *
581         * @param path     The relative path to the desired properties file.
582         * @param property The name of the required property.
583         *
584         * @return The value of the named property within the properties file.  Returns
585         *         null if the property doesn't exist or the properties file doesn't exist.
586         */
587        public static String lookupByJREPropertyFile(String path, String property) throws IOException {
588            String jreDirectory = System.getProperty("java.home");
589            File configurationFile = new File(jreDirectory + File.separator + path);
590            if (configurationFile.exists() && configurationFile.canRead()) {
591                Properties properties = new Properties();
592                InputStream in = null;
593                try {
594                    in = new FileInputStream(configurationFile);
595                    properties.load(in);
596                    return properties.getProperty(property);
597                } finally {
598                    if (in != null) {
599                        try {
600                            in.close();
601                        } catch (Exception e) {
602                        }
603                    }
604                }
605            }
606            return null;
607        }
608    
609    
610        /**
611         * Retrieve the registry from the tracker if it is available,
612         * all without causing the interface class to load.
613         *
614         * @return The registry service instance, or null if it is not
615         *         available for any reason.
616         */
617        private static Object getRegistry() {
618            // if not initialized in an OSGi environment, this is a failure
619            if (registryTracker == null) {
620                return null;
621            }
622            // get the service, if it exists.  NB:  it is only safe to reference the
623            // interface class if the tracker returns a non-null service object.  The
624            // interface class will not be loaded in our bundle context until the
625            // service class can be statisfied.  Therefore, we always return this as
626            // just an object and the call needs to perform the cast, which will
627            // force the classload at that time.
628            return ((ServiceTracker)registryTracker).getService();
629        }
630    }