blob: 1f7d28d5f1a8825cae27bf10242f043359d8d1bb [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.yoko.osgi;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
public class ProviderLocator {
// a service tracker for the registry service
// NB: This is declared as just Object to avoid classloading issues if we're running
// outside of an OSGi environment.
static private ProviderRegistry registry;
private ProviderLocator() {
// private constructor to prevent an instance from getting created.
}
public static void setRegistry(ProviderRegistry registry) {
ProviderLocator.registry = registry;
}
/**
* Locate a class by its provider id indicator. .
*
* @param providerId The provider id (generally, a fully qualified class name).
*
* @return The Class corresponding to this provider id. Returns null
* if this is not registered or the indicated class can't be
* loaded.
*/
static public Class<?> locate(String providerId) {
ProviderRegistry registry = getRegistry();
// if no registry service available, this is a failure
if (registry == null) {
return null;
}
// get the service, if it exists. NB, if there is a service object,
// then the extender and the interface class are available, so this cast should be
// safe now.
// the rest of the work is done by the registry
return registry.locate(providerId);
}
/**
* Locate all class files that match a given factory id.
*
* @param providerId The target provider identifier.
*
* @return A List containing the class objects corresponding to the
* provider identifier. Returns an empty list if no
* matching classes can be located.
*/
static public List<Class<?>> locateAll(String providerId) {
Object registry = getRegistry();
// if no registry service available, this is a failure
if (registry == null) {
return new ArrayList<Class<?>>();
}
// get the service, if it exists. NB, if there is a service object,
// then the extender and the interface class are available, so this cast should be
// safe now.
// the rest of the work is done by the registry
return ((ProviderRegistry)registry).locateAll(providerId);
}
/**
* Utility class for locating a class with OSGi registry
* support. Uses the thread context classloader as part of
* the search order.
*
* @param className The name of the target class.
*
* @return The loaded class.
* @exception ClassNotFoundException
* Thrown if the class cannot be located.
*/
static public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, null, Thread.currentThread().getContextClassLoader());
}
/**
* Utility class for locating a class with OSGi registry
* support. Uses the thread context classloader as part of
* the search order.
*
* @param className The name of the target class.
*
* @return The loaded class.
* @exception ClassNotFoundException
* Thrown if the class cannot be located.
*/
static public Class<?> loadClass(String className, Class<?> contextClass) throws ClassNotFoundException {
return loadClass(className, contextClass, Thread.currentThread().getContextClassLoader());
}
/**
* Standardized utility method for performing class lookups
* with support for OSGi registry lookups.
*
* @param className The name of the target class.
* @param loader An optional class loader.
*
* @return The loaded class
* @exception ClassNotFoundException
* Thrown if the class cannot be loaded.
*/
static public Class<?> loadClass(String className, Class<?>contextClass, ClassLoader loader) throws ClassNotFoundException {
// ideally, this should be last. However, some of the bundles duplicate classes
// found on the boot delegation, so we need to check this first to keep
// from picking up one of the default implementations.
Class cls = locate(className);
if (cls != null) {
return cls;
}
if (loader != null) {
try {
return loader.loadClass(className);
} catch (ClassNotFoundException x) {
}
}
if (contextClass != null) {
loader = contextClass.getClassLoader();
}
// try again using the class context loader
return Class.forName(className, true, loader);
}
/**
* Get a single service instance that matches an interface
* definition.
*
* @param iface The name of the required interface.
* @param contextClass
* The class requesting the lookup (used for class resolution).
* @param loader A class loader to use for searching for service definitions
* and loading classes.
*
* @return The service instance, or null if no matching services
* can be found.
* @exception Exception Thrown for any classloading or exceptions thrown
* trying to instantiate a service instance.
*/
static public Object getService(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
// if we are working in an OSGi environment, then process the service
// registry first. Ideally, we would do this last, but because of boot delegation
// issues with some API implementations, we must try the OSGi version first
Object registry = getRegistry();
if (registry != null) {
// get the service, if it exists. NB, if there is a service object,
// then the extender and the interface class are available, so this cast should be
// safe now.
// the rest of the work is done by the registry
Object service = ((ProviderRegistry)registry).getService(iface);
if (service != null) {
return service;
}
}
// try for a classpath locatable instance next. If we find an appropriate class mapping,
// create an instance and return it.
Class<?> cls = locateServiceClass(iface, contextClass, loader);
if (cls != null) {
return cls.newInstance();
}
// a provider was not found
return null;
}
/**
* Locate a service class that matches an interface
* definition.
*
* @param iface The name of the required interface.
* @param contextClass
* The class requesting the lookup (used for class resolution).
* @param loader A class loader to use for searching for service definitions
* and loading classes.
*
* @return The located class, or null if no matching services
* can be found.
* @exception Exception Thrown for any classloading exceptions thrown
* trying to load the class.
*/
static public Class<?> getServiceClass(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
// if we are working in an OSGi environment, then process the service
// registry first. Ideally, we would do this last, but because of boot delegation
// issues with some API implementations, we must try the OSGi version first
Object registry = getRegistry();
if (registry != null) {
// get the service, if it exists. NB, if there is a service object,
// then the extender and the interface class are available, so this cast should be
// safe now.
// If we've located stuff in the registry, then return it
Class<?> cls = ((ProviderRegistry)registry).getServiceClass(iface);
if (cls != null) {
return cls;
}
}
// try for a classpath locatable instance first. If we find an appropriate class mapping,
// create an instance and return it.
return locateServiceClass(iface, contextClass, loader);
}
/**
* Get a list of services that match a given interface
* name. This searches both the current class path and
* the global repository for matches.
*
* @param iface The name of the required interface.
* @param contextClass
* The class requesting the lookup (used for class resolution).
* @param loader A class loader to use for searching for service definitions
* and loading classes.
*
* @return A list of matching services. Returns an empty list if there
* are no matches.
* @exception Exception Thrown for any classloading or exceptions thrown
* trying to instantiate a service instance.
*/
static public List<Object> getServices(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
List<Object> services = new ArrayList<Object>();
// because of boot delegation issues with some of the API implementations, it is necessary
// to process the OSGi registered versions first to allow override of JRE provided APIs.
Object registry = getRegistry();
if (registry != null) {
// get the service, if it exists. NB, if there is a service object,
// then the extender and the interface class are available, so this cast should be
// safe now.
// get any registered service instances now
List<Object> globalServices = ((ProviderRegistry)registry).getServices(iface);
// add to our list also
if (globalServices != null) {
services.addAll(globalServices);
}
}
// try for a classpath locatable instance second. If we find an appropriate class mapping,
// create an instance and return it.
Collection<Class<?>> classes = locateServiceClasses(iface, contextClass, loader);
if (classes != null) {
// create an instance of each of these classes
for (Class<?> cls : classes) {
services.add(cls.newInstance());
}
}
// now return the merged set
return services;
}
/**
* Get a list of service class implementations that match
* an interface name. This searches both the current class path and
* the global repository for matches.
*
* @param iface The name of the required interface.
* @param contextClass
* The class requesting the lookup (used for class resolution).
* @param loader A class loader to use for searching for service definitions
* and loading classes.
*
* @return A list of matching provider classes. Returns an empty list if there
* are no matches.
* @exception Exception Thrown for any classloading exceptions thrown
* trying to load a provider class.
*/
static public List<Class<?>> getServiceClasses(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
Set<Class<?>> serviceClasses = new LinkedHashSet<Class<?>>();
// because of boot delegation issues with some of the API implementations, it is necessary
// to process the OSGi registered versions first to allow override of JRE provided APIs.
Object registry = getRegistry();
if (registry != null) {
// get the service, if it exists. NB, if there is a service object,
// then the extender and the interface class are available, so this cast should be
// safe now.
// get any registered service provider classes now
List<Class<?>> globalServices = ((ProviderRegistry)registry).getServiceClasses(iface);
// add to our list also
if (globalServices != null) {
serviceClasses.addAll(globalServices);
}
}
// try for a classpath locatable classes second. If we find an appropriate class mapping,
// add this to our return collection.
Collection<Class<?>> classes = locateServiceClasses(iface, contextClass, loader);
if (classes != null) {
serviceClasses.addAll(classes);
}
// now return the merged set
return new ArrayList(serviceClasses);
}
/**
* Locate the first class name for a META-INF/services definition
* of a given class. The first matching provider is
* returned.
*
* @param iface The interface class name used for the match.
* @param loader The classloader for locating resources.
*
* @return The mapped provider name, if found. Returns null if
* no mapping is located.
*/
static private String locateServiceClassName(String iface, Class<?> contextClass, ClassLoader loader) {
// search first with the loader class path
String name = locateServiceClassName(iface, loader);
if (name != null) {
return name;
}
// then with the context class, if there is one
if (contextClass != null) {
name = locateServiceClassName(iface, contextClass.getClassLoader());
if (name != null) {
return name;
}
}
// not found
return null;
}
/**
* Locate a classpath-define service mapping.
*
* @param iface The required interface name.
* @param loader The ClassLoader instance to use to locate the service.
*
* @return The mapped class name, if one is found. Returns null if the
* mapping is not located.
*/
static private String locateServiceClassName(String iface, ClassLoader loader) {
if (loader != null) {
try {
// we only look at resources that match the file name, using the specified loader
String service = "META-INF/services/" + iface;
Enumeration<URL> providers = loader.getResources(service);
while (providers.hasMoreElements()) {
List<String>providerNames = parseServiceDefinition(providers.nextElement());
// if there is something defined here, return the first entry
if (!providerNames.isEmpty()) {
return providerNames.get(0);
}
}
} catch (IOException e) {
}
}
// not found
return null;
}
/**
* Locate the first class for a META-INF/services definition
* of a given interface class. The first matching provider is
* returned.
*
* @param iface The interface class name used for the match.
* @param loader The classloader for locating resources.
*
* @return The mapped provider class, if found. Returns null if
* no mapping is located.
*/
static private Class<?> locateServiceClass(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
String className = locateServiceClassName(iface, contextClass, loader);
if (className == null) {
return null;
}
// we found a name, try loading the class. This will throw an exception if there is an error
return loadClass(className, contextClass, loader);
}
/**
* Locate all class names name for a META-INF/services definition
* of a given class.
*
* @param iface The interface class name used for the match.
* @param loader The classloader for locating resources.
*
* @return The mapped provider name, if found. Returns null if
* no mapping is located.
*/
static private Collection<String> locateServiceClassNames(String iface, Class<?> contextClass, ClassLoader loader) {
Set<String> names = new LinkedHashSet<String>();
locateServiceClassNames(iface, loader, names);
if (contextClass != null) {
locateServiceClassNames(iface, contextClass.getClassLoader(), names);
}
return names;
}
/**
* Locate all class names name for a META-INF/services definition
* of a given class.
*
* @param iface The interface class name used for the match.
* @param loader The classloader for locating resources.
*
* @return The mapped provider name, if found. Returns null if
* no mapping is located.
*/
static void locateServiceClassNames(String iface, ClassLoader loader, Set names) {
if (loader != null) {
try {
// we only look at resources that match the file name, using the specified loader
String service = "META-INF/services/" + iface;
Enumeration<URL> providers = loader.getResources(service);
while (providers.hasMoreElements()) {
List<String>providerNames = parseServiceDefinition(providers.nextElement());
// just add all of these to the list
names.addAll(providerNames);
}
} catch (IOException e) {
}
}
}
/**
* Locate all classes that map to a given provider class definition. This will
* search both the services directories, as well as the provider classes from the
* OSGi provider registry.
*
* @param iface The interface class name used for the match.
* @param loader The classloader for locating resources.
*
* @return A list of all mapped classes, if found. Returns an empty list if
* no mappings are found.
*/
static private Collection<Class<?>> locateServiceClasses(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
// get the set of names from services definitions on the classpath
Collection<String> classNames = locateServiceClassNames(iface, contextClass, loader);
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// load each class and add to our return set
for (String name : classNames) {
classes.add(loadClass(name, contextClass, loader));
}
return classes;
}
/**
* Parse a definition file and return the names of all included implementation classes
* contained within the file.
*
* @param u The URL of the file
*
* @return A list of all matching classes. Returns an empty list
* if no matches are found.
*/
static private List<String> parseServiceDefinition(URL u) {
final String url = u.toString();
List<String> classes = new ArrayList<String>();
// ignore directories
if (url.endsWith("/")) {
return classes;
}
// the identifier used for the provider is the last item in the URL.
final String providerId = url.substring(url.lastIndexOf("/") + 1);
try {
BufferedReader br = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8"));
// the file can be multiple lines long, with comments. A single file can define multiple providers
// for a single key, so we might need to create multiple entries. If the file does not contain any
// definition lines, then as a default, we use the providerId as an implementation class also.
String line = br.readLine();
while (line != null) {
// we allow comments on these lines, and a line can be all comment
int comment = line.indexOf('#');
if (comment != -1) {
line = line.substring(0, comment);
}
line = line.trim();
// if there is nothing left on the line after stripping white space and comments, skip this
if (line.length() > 0) {
// add this to our list
classes.add(line);
}
// keep reading until the end.
line = br.readLine();
}
br.close();
} catch (IOException e) {
// ignore errors and handle as default
}
return classes;
}
/**
* Perform a service class discovery by looking for a
* property in a target properties file located in the
* java.home directory.
*
* @param path The relative path to the desired properties file.
* @param property The name of the required property.
*
* @return The value of the named property within the properties file. Returns
* null if the property doesn't exist or the properties file doesn't exist.
*/
public static String lookupByJREPropertyFile(String path, String property) throws IOException {
String jreDirectory = System.getProperty("java.home");
File configurationFile = new File(jreDirectory + File.separator + path);
if (configurationFile.exists() && configurationFile.canRead()) {
Properties properties = new Properties();
InputStream in = null;
try {
in = new FileInputStream(configurationFile);
properties.load(in);
return properties.getProperty(property);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
}
}
}
}
return null;
}
/**
* Retrieve the registry from the tracker if it is available,
* all without causing the interface class to load.
*
* @return The registry service instance, or null if it is not
* available for any reason.
*/
private static ProviderRegistry getRegistry() {
return registry;
}
}