/** * 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.geronimo.osgi.registry; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.osgi.framework.Bundle; import org.osgi.service.log.LogService; /** * The implementation of the provider registry used to store * the bundle registrations. */ public class ProviderRegistryImpl implements org.apache.geronimo.osgi.registry.api.ProviderRegistry { // indicates a bundle wishes to opt in to the META-INF/services registration and tracking. public static final String OPT_IN_HEADER = "SPI-Provider"; // provider classes exported via a header. public static final String EXPORT_PROVIDER_HEADER = "Export-SPI-Provider"; // our mapping between a provider id and the implementation information. There // might be a one-to-many relationship between the ids and implementing classes. private SPIRegistry providers = new SPIRegistry(); // our mapping between an interface name and a META-INF/services SPI implementation. There // might be a one-to-many relationship between the ids and implementing classes. private SPIRegistry serviceProviders = new SPIRegistry(); // our base Activator (used as a service source) private Activator activator; public ProviderRegistryImpl(Activator activator) { this.activator = activator; } /** * Add a bundle to the provider registry. This searches * for services information in the OSGI-INF/providers * directory of the bundle and registers this information * in a provider registry. Bundles that need to locate * class instances can use the provider registry to * locate classes that might reside in other bundles. * * @param bundle The source bundle. * * @return A map of the located registrations. Returns null if * this bundle does not contain any providers. */ public Object addBundle(Bundle bundle) { log(LogService.LOG_DEBUG, "adding bundle " + bundle); // create a tracker item for this bundle. This will record all of the information // that's relevent to this bundle BundleResources tracker = new BundleResources(bundle); // if the tracker found information of interest, return it to the // BundleTracker to let it know we need to watch this one. return tracker.needsTracking() ? tracker : null; } /** * Remove a bundle from the registry. * * @param bundle The target bundle. */ public void removeBundle(Bundle bundle, Object obj) { log(LogService.LOG_DEBUG, "removing bundle " + bundle); BundleResources tracker = (BundleResources)obj; if (tracker != null) { tracker.remove(); } } /** * Register an individual provivider item by its provider identifier. * * @param id The provider id. * @param provider The loader used to resolve the provider class. */ protected void registerProvider(BundleProviderLoader provider) { log(LogService.LOG_DEBUG, "registering provider " + provider); providers.register(provider); } /** * Removed a provider registration for a named provider id. * * @param id The target id * @param provider The provider registration instance */ protected void unregisterProvider(BundleProviderLoader provider) { log(LogService.LOG_DEBUG, "unregistering provider " + provider); providers.unregister(provider); } /** * Register an individual provivider item by its provider identifier. * * @param id The provider id. * @param provider The loader used to resolve the provider class. */ protected void registerService(BundleProviderLoader provider) { log(LogService.LOG_DEBUG, "registering service " + provider); serviceProviders.register(provider); } /** * Removed a provider registration for a named provider id. * * @param id The target id * @param provider The provider registration instance */ protected void unregisterService(BundleProviderLoader provider) { log(LogService.LOG_DEBUG, "unregistering service " + provider); serviceProviders.unregister(provider); } /** * 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. */ public Class<?> locate(String providerId) { // see if we have a registered match for this...getting just the first instance BundleProviderLoader loader = providers.getLoader(providerId); if (loader != null) { try { // try to load this. We always return null return loader.loadClass(); } catch (Exception e) { e.printStackTrace(); // just swallow this and return null. The exception has already // been logged. } } // no match to return return null; } /** * Locate all class files that match a given provider 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. */ public List<Class<?>> locateAll(String providerId) { List<Class<?>> classes = new ArrayList<Class<?>>(); List<BundleProviderLoader> l = providers.getLoaders(providerId); // this returns null if nothing is found. if (l != null) { for (BundleProviderLoader c : l) { try { classes.add(c.loadClass()); } catch (Exception e) { // just swallow this and proceed to the next. The exception has // already been logged. } } } return classes; } /** * Locate and instantiate an instance of a service provider * defined in the META-INF/services directory of tracked bundles. * * @param providerId The name of the target interface class. * * @return The service instance. Returns null if no service defintions * can be located. * @exception Exception Any classloading or other exceptions thrown during * the process of creating this service instance. */ public Object getService(String providerId) throws Exception { List<BundleProviderLoader> loaders = serviceProviders.getLoaders(providerId); if (loaders == null || loaders.size() == 0) { return null; } String preferenceProviderClassName = System.getProperty(providerId); if (preferenceProviderClassName != null) { for (BundleProviderLoader loader : loaders) { if (loader.providerClass.equals(preferenceProviderClassName)) { return loader.createInstance(); } } } return loaders.get(0).createInstance(); } /** * Locate all services that match a given provider id and create instances. * * @param providerId The target provider identifier. * * @return A List containing the instances corresponding to the * provider identifier. Returns an empty list if no * matching classes can be located or created */ public List<Object> getServices(String providerId) { List<Object> instances = new ArrayList<Object>(); List<BundleProviderLoader> l = serviceProviders.getLoaders(providerId); // this returns null for nothing found if (l != null) { for (BundleProviderLoader c : l) { try { instances.add(c.createInstance()); } catch (Exception e) { // just swallow this and proceed to the next. The exception has // already been logged. } } } return instances; } /** * Locate all services that match a given provider id and return the implementation * classes * * @param providerId The target provider identifier. * * @return A List containing the classes corresponding to the * provider identifier. Returns an empty list if no * matching classes can be located. */ public List<Class<?>> getServiceClasses(String providerId) { List<Class<?>> classes = new ArrayList<Class<?>>(); List<BundleProviderLoader> l = serviceProviders.getLoaders(providerId); // this returns null for nothing found if (l != null) { for (BundleProviderLoader c : l) { try { classes.add(c.loadClass()); } catch (Exception e) { e.printStackTrace(); // just swallow this and proceed to the next. The exception has // already been logged. } } } return classes; } /** * Locate and return the class for a service provider * defined in the META-INF/services directory of tracked bundles. * * @param providerId The name of the target interface class. * * @return The provider class. Returns null if no service defintions * can be located. * @exception Exception Any classloading or other exceptions thrown during * the process of loading this service provider class. */ public Class<?> getServiceClass(String providerId) throws ClassNotFoundException { List<BundleProviderLoader> loaders = serviceProviders.getLoaders(providerId); if (loaders == null || loaders.size() == 0) { return null; } String preferenceProviderClassName = System.getProperty(providerId); if (preferenceProviderClassName != null) { for (BundleProviderLoader loader : loaders) { if (loader.providerClass.equals(preferenceProviderClassName)) { return loader.loadClass(); } } } return loaders.get(0).loadClass(); } private void log(int level, String message) { activator.log(level, message); } private void log(int level, String message, Throwable th) { activator.log(level, message, th); } private class BundleResources { // the bundle we're attached to. private Bundle bundle; // our map of providers maintained for the META-INF/services design pattern. // this is an interface-to-provider instance mapping. private List<BundleProviderLoader> serviceProviders; // the defined mapping for provider classes...not maintained as an // interface-to-provider mapping. private List<BundleProviderLoader> providers; public BundleResources(Bundle b) { bundle = b; // go locate any services we need locateProviders(); locateServices(); } public boolean needsTracking() { return serviceProviders != null || providers != null; } // locate and process any providers defined in the OSGI-INF/providers directory private void locateProviders() { // we accumulate from the headers and the providers directory. The headers // are simpler if there is no class mapping and is easier to use when // converting a simple jar to a bundle. Set<BundleProviderLoader> locatedProviders = new LinkedHashSet<BundleProviderLoader>(); List<BundleProviderLoader> headerProviders = locateHeaderProviderDefinitions(); if (headerProviders != null) { locatedProviders.addAll(headerProviders); } List<BundleProviderLoader> directoryProviders = processDefinitions("OSGI-INF/providers/"); if (directoryProviders != null) { locatedProviders.addAll(directoryProviders); } // if we have anything, add to global registry if (!locatedProviders.isEmpty()) { // process the registrations for each item for (BundleProviderLoader loader: locatedProviders) { // add to the mapping table registerProvider(loader); } // remember this list so we can unregister when the bundle is stopped providers = new ArrayList<BundleProviderLoader>(locatedProviders); } } /** * Parse the Export-Provider: header to create a list of * providers that are exported via the header syntax * rather than via a provider mapping file. * * @return A list of providers defined on the header, or null if * no providers were exported. */ private List<BundleProviderLoader> locateHeaderProviderDefinitions() { // check the header to see if there's anything defined here. String exportedProviders = (String)bundle.getHeaders().get(EXPORT_PROVIDER_HEADER); if (exportedProviders == null) { return null; } List<BundleProviderLoader>providers = new ArrayList<BundleProviderLoader>(); // split on the separator String[] classNames = exportedProviders.split(","); for (String name : classNames) { name = name.trim(); // this is a simple mapping providers.add(new BundleProviderLoader(name, name, bundle)); } return providers; } // now process any services private void locateServices() { // we only process these if there is a header indicating this // bundle wants to opt-in to this registration process. if (bundle.getHeaders().get(OPT_IN_HEADER) == null) { return; } log(LogService.LOG_INFO, OPT_IN_HEADER + " Manifest header found in bundle: " + bundle.getSymbolicName()); serviceProviders = processDefinitions("META-INF/services/"); // if we have anything, add to global registry if (serviceProviders != null) { // process the registrations for each item for (BundleProviderLoader loader: serviceProviders) { // add to the mapping table registerService(loader); } } } /** * Remove all resources associated with this bundle from the * global registry. */ public void remove() { log(LogService.LOG_DEBUG, "removing bundle " + bundle); if (providers != null) { for (BundleProviderLoader loader : providers) { // unregistry the individual entry unregisterProvider(loader); } } if (serviceProviders != null) { for (BundleProviderLoader loader : serviceProviders) { // unregistry the individual entry unregisterService(loader); } } } /** * Process all of the service definition files in a given * target path. This is used to process both the * META-INF/services files and the OSGI-INF/providers files. * * @param path The target path location. * * @return The list of matching service definitions. Returns null if * no matches were found. */ private List<BundleProviderLoader> processDefinitions(String path) { List<BundleProviderLoader> mappings = new ArrayList<BundleProviderLoader>(); // look for services definitions in the bundle...we accumulate these as provider class // definitions. @SuppressWarnings("unchecked") Enumeration<URL> e = bundle.findEntries(path, "*", false); if (e != null) { while (e.hasMoreElements()) { final URL u = e.nextElement(); // go parse out the control file parseServiceFile(u, mappings); } } // only return this if we have something associated with this bundle return mappings.isEmpty() ? null : mappings; } /** * Parse a provider definition file and create loaders * for all definitions contained within the file. * * @param u The URL of the file * * @return A list of the defined mappings. */ private void parseServiceFile(URL u, List<BundleProviderLoader>mappings) { final String url = u.toString(); // ignore directories if (url.endsWith("/")) { return; } // 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 mappings.add(new BundleProviderLoader(providerId, line, bundle)); } // keep reading until the end. line = br.readLine(); } br.close(); } catch (IOException e) { // ignore errors and handle as default } } } /** * Holder class for information about a given collection of * id to provider mappings. Used for both the providers and * the services. */ private class SPIRegistry { private Map<String, List<BundleProviderLoader>> registry; /** * Register an individual provivider item by its provider identifier. * * @param id The provider id. * @param provider The loader used to resolve the provider class. */ public synchronized void register(BundleProviderLoader provider) { // if this is the first registration, create the mapping table if (registry == null) { registry = new HashMap<String, List<BundleProviderLoader>>(); } String providerId = provider.id(); // the providers are stored as a list...we use the first one registered // when asked to locate. List<BundleProviderLoader> l = registry.get(providerId); if (l == null) { l = new ArrayList<BundleProviderLoader>(); registry.put(providerId, l); } l.add(provider); } /** * Remove a provider registration for a named provider id. * * @param provider The provider registration instance */ public synchronized void unregister(BundleProviderLoader provider) { if (registry != null) { // this is stored as a list. Just remove using the registration information // This may move a different provider to the front of the list. List<BundleProviderLoader> l = registry.get(provider.id()); if (l != null) { l.remove(provider); } } } private synchronized BundleProviderLoader getLoader(String id) { // synchronize on the registry instance if (registry != null) { // return the first match, if any List<BundleProviderLoader> list = registry.get(id); if (list != null && !list.isEmpty()) { return list.get(0); } } // no match here return null; } private synchronized List<BundleProviderLoader> getLoaders(String id) { if (registry != null) { // if we have matches, return a copy of what we currently have // to create a safe local copy. List<BundleProviderLoader> list = registry.get(id); if (list != null && !list.isEmpty()) { return new ArrayList<BundleProviderLoader>(list); } } // no match here return null; } } /** * Holder class for located services information. */ private class BundleProviderLoader { // the class name for this provider private final String providerId; // the mapped class name of the provider. private final String providerClass; // the hosting bundle. private final Bundle bundle; /** * Create a loader for this registered provider. * * @param providerId The provider ID * @param providerClass The mapped class name of the provider. * @param bundle The hosting bundle. */ public BundleProviderLoader(String providerId, String providerClass, Bundle bundle) { this.providerId = providerId; this.providerClass = providerClass; this.bundle = bundle; } /** * Load a provider class. * * @return The provider class from the target bundle. * @exception Exception */ public Class<?> loadClass() throws ClassNotFoundException { try { log(LogService.LOG_DEBUG, "loading class for: " + this); return bundle.loadClass(providerClass); } catch (ClassNotFoundException e) { log(LogService.LOG_DEBUG, "exception caught while loading " + this, e); throw e; } } /** * Create an instance of the registred service. * * @return The created instance. A new instance is created on each call. * @exception Exception */ public Object createInstance() throws Exception { // get the class object Class <?> cls = loadClass(); try { // just create an instance using the default constructor return cls.newInstance(); } catch (Exception e) { log(LogService.LOG_DEBUG, "exception caught while creating " + this, e); throw e; } catch (Error e) { log(LogService.LOG_DEBUG, "error caught while creating " + this, e); throw e; } } public String id() { return providerId; } @Override public String toString() { return "Provider interface=" + providerId + " , provider class=" + providerClass + ", bundle=" + bundle; } @Override public int hashCode() { return providerId.hashCode() + providerClass.hashCode() + (int)bundle.getBundleId(); } @Override public boolean equals(Object obj) { if (obj instanceof BundleProviderLoader) { return providerId.equals(((BundleProviderLoader)obj).providerId) && providerClass.equals(((BundleProviderLoader)obj).providerClass) && bundle.getBundleId() == ((BundleProviderLoader)obj).bundle.getBundleId(); } else { return false; } } } }