/* * © Copyright IBM Corp. 2012-2013 * * Licensed 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 com.ibm.commons.extension; 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.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.osgi.framework.Bundle; import com.ibm.commons.log.CommonsLogger; import com.ibm.commons.util.StringUtil; import com.ibm.commons.util.TDiag; import com.ibm.commons.util.io.StreamUtil; /** * Extension Manager. * * <p>This class is used to manage the extension in an independant manner. It can * use services like extension or Eclipse extension points, depending on the * running platform.</p> * * <p>As the runtime can be shared by multiple J2EE applications, some extensions * can be defined globally (e.g. for all the applications) or on a per application * basis.</p> * * @ibm-api */ public class ExtensionManager { /** * Interface that defines an application classloader. * An ApplicationClassLoader is used to load application specific resources. * * Not intended to be subclassed * @ibm-not-published */ public interface ApplicationClassLoader { public Enumeration<URL> findApplicationResources(String resName) throws IOException; } private static Provider provider; static { try { // We should detect if we are running in an eclipse environment // We cannot use the platform object for this, as the platform object is using the // extension manager itself to get the actual class to load. // => Platform.getInstance().isEclipseBased() doesn't work properly // Moreover, in case of the Domino server, the OSGI integration sets the OSGi flgas // while we should use the Java services manager. for this reason, we have a flag // to set OSGi properly. try { String osgi = System.getProperty("osgi.framework.version"); // $NON-NLS-1$ if(StringUtil.isNotEmpty(osgi)) { try { String prop = System.getProperty("com.ibm.common.ExtensionManager"); //$NON-NLS-1$ if(StringUtil.isEmpty(prop) || StringUtil.equals(prop, "osgi")) { //$NON-NLS-1$ // Make sure that the OSGi framework is actually available // In case of a Domino agent, for example, the properties are set but OSGi is not available // Make sure that it exists - if not, it generates an exception that will be caught. Class.forName("org.eclipse.core.runtime.Platform"); provider = new EclipseProvider(); } } catch(Throwable t) {} } } catch (Throwable e) {} if(provider==null) { provider = new JavaServiceProvider(); } } catch(Throwable t) { t.printStackTrace(); } } /** * Find the available implementations of a service for a particular Application. * * <p>This includes both the globally available services as well as the one defined in the * application class loader as well. The global services are generally read from the * Eclipse extension points while the per application are read using the services * definition (until OSGi will be available everywhere....)</p> * <p>The result is a list of service implementations (concrete classes)</p> * @param loader the class loader used to load the services * @param serviceType the name of the service to load * @return a List of services implementation * @ibm-api */ public static List<Object> findApplicationServices(ClassLoader loader, String serviceType) { return findApplicationServices(null,loader,serviceType); } /** * Find the available implementations of a service for a particular Application. * * <p>Similar to com.ibm.commons.extension.ExtensionManager.findApplicationServices(ClassLoader, String) * but it also manages a cache which prevents the resources to be read if not * necessary.</p> * * @param services the Map that holds the cache. Must be one instance per Application * @param loader the class loader used to load the services * @param serviceType the name of the service to load * @return a List of services implementation * @ibm-api */ public static List<Object> findApplicationServices(Map<String, List<Object>> services, ClassLoader loader, String serviceType) { if(services!=null) { List<Object> l = services.get(serviceType); if(l!=null) { return l; } } List<Object> l = loadServices(loader,serviceType); if(services!=null) { services.put(serviceType,l); } return l; } /** * Find the globally available implementations of a service. * * <p>The list only contained the services that are shared, and not the application * specific ones.</p> * * @param list an optional list containing the services. If not null, then it is returned * @param clazz the class to get the class loader for loading the services * @param serviceType the name of the service to load * @return a List of services implementation * @ibm-api */ public static List<Object> findServices(List<Object> list, Class<?> clazz, String serviceType) { return findServices(list,clazz.getClassLoader(),serviceType); } /** * Find the globally available implementations of a service. * * <p>The list only contained the services that are shared, and not the application * specific ones.</p> * * @param list an optional list containing the services. If not null, then it is returned * @param classloader The class loader to look for the services * @param serviceType the name of the service to load * @return a List of services implementation * @ibm-api */ public static List<Object> findServices(List<Object> list, ClassLoader loader, String serviceType) { if(list!=null) { return list; } // In case of an application class loader, ignores it // Note that this is only for a Domino environment and the XPages runtime ApplicationClassLoader ac = adaptApplicationClassloader(loader); if(ac!=null) { loader = loader.getParent(); } list = loadServices(loader,serviceType); return list; } /** * Find the globally available implementations of a service. * * <p>This is the type safety implementation of the method using generics. The content * of the list is checked to ensure that all the implementations belong to the correct * type.</p> * * @param <T> * @param existingList * already loaded service list; the services will only be loaded * if list is <code>null</code>. * @param loader * ClassLoader to use when loading the classes in a non-OSGI * environment. * @param serviceType * service id. * @param requiredType * {@link Class} to be implemented by all contributing services. * Services which do not implement that class will generate a * warning and will not be included in the result {@link List}. * When <code>null</code> no class validation is performed. * @return * @ibm-api */ @SuppressWarnings("unchecked")//$NON-NLS-1$ public static <T> List<T> findServices(List<T> existingList, ClassLoader loader, String serviceType, Class<T> requiredType){ if(existingList!=null) { return (List<T>) existingList; } List<T> items = (List<T>)findServices(null, loader, serviceType); if( null != requiredType){ for (int i = 0; i < items.size(); i++) { Object item = items.get(i); if( ! (requiredType.isAssignableFrom(item.getClass())) ){ logServiceNotInstanceOf(serviceType, item, requiredType); items.remove(i); i--; } } } return items; } private static void logServiceNotInstanceOf(String extensionPointId, Object obj, Class<?> requiredType) { // this is a problem if( CommonsLogger.STANDARD.isWarnEnabled() ){ String warnMsg = "The object {0} cannot contribute to the service {1}, as it is not an instance of {2}"; // $NLW-ExtensionManager.ServiceObjectNotInstanceOfExpectedClass-1$ CommonsLogger.STANDARD.warnp(ExtensionManager.class, "logClassNotLibrary", warnMsg,//$NON-NLS-1$ obj, extensionPointId, requiredType.getName()); } } /** * Load the available implementation of a service for a particular class loader. */ private static List<Object> loadServices(final ClassLoader loader, final String serviceType) { List<Object> list = new ArrayList<Object>(); provider.findInitializer(loader, list, serviceType); return list; } private interface Provider { public void findInitializer(ClassLoader loader, List<Object> initializers, String serviceType); } private static class JavaServiceProvider implements Provider { final String PREFIX = "META-INF/services/"; //$NON-NLS-1$ public void findInitializer(ClassLoader loader, List<Object> initializers, String serviceType) { try { //PHIL: JDK 1.6 is providing a class to do that: java.util.ServiceLoader. Might // use that later // Look for all the resources in the class path Enumeration<URL> e = getResourcesList(loader, serviceType); if(e!=null) { while( e.hasMoreElements() ) { URL url = e.nextElement(); parseResource(loader,initializers,url, serviceType); } } } catch( Throwable t ) { t.printStackTrace(); } } Enumeration<URL> getResourcesList(ClassLoader loader, String serviceType) throws IOException { return loader.getResources(PREFIX+serviceType); } void parseResource(ClassLoader loader, List<Object> initializers, URL url, String serviceType) throws IOException { BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); //$NON-NLS-1$ try { while(true) { String s = r.readLine(); if(s==null) { return; } int comment = s.indexOf('#'); if(comment>=0) { s = s.substring(0,comment); } s = s.trim(); // Try to load the class if(StringUtil.isNotEmpty(s)) { try { Class<?> c = loader.loadClass(s); Object o = c.newInstance(); initializers.add(o); } catch(Throwable ex) { logCouldNotCreateService(ex, s, serviceType); TDiag.trace("Designer runtime: Error while parsing service file {0}", url.toString()); //$NON-NLS-1$ } } } } finally { StreamUtil.close(r); } } private void logCouldNotCreateService(Throwable ex, String className, String serviceType) { if( CommonsLogger.STANDARD.isWarnEnabled() ){ String warnMsg = "Could not create an instance of {0}, contributed to the service {1}."; // $NLW-ExtensionManager.Couldnotcreateaninstanceof0contri.1-1$ CommonsLogger.STANDARD.warnp(this, "logCouldNotCreateService", ex, warnMsg, //$NON-NLS-1$ className, serviceType); } } } private static class EclipseProvider extends JavaServiceProvider { public void findInitializer(ClassLoader loader, List<Object> initializers, String serviceType) { // Load the global providers, from the platform try { IExtensionRegistry reg = org.eclipse.core.runtime.Platform.getExtensionRegistry(); if (reg != null) { // Services defined individually // Look if someone implemented an extension point to retrieve the platform IConfigurationElement[] elt = reg.getConfigurationElementsFor("com.ibm.commons.Extension"); // $NON-NLS-1$ for( int i=0; i<elt.length; i++ ) { if( "service".equalsIgnoreCase(elt[i].getName()) ) { // $NON-NLS-1$ String type = elt[i].getAttribute("type"); //$NON-NLS-1$ if(StringUtil.equals(type, serviceType)) { try { Object o = elt[i].createExecutableExtension("class"); // $NON-NLS-1$ initializers.add(o); } catch(Throwable ex) { logCouldNotCreateContribution(ex, elt[i], type); } } } } // Services read from META-INF/services // Set<URL> urls = new HashSet<URL>(); elt = reg.getConfigurationElementsFor("com.ibm.commons.ExtensionBundle"); // $NON-NLS-1$ for( int i=0; i<elt.length; i++ ) { try { OSGiExtensionService o = (OSGiExtensionService)elt[i].createExecutableExtension("class"); Bundle bundle = o.getBundle(); if(bundle!=null) { findServicesClassLoader(o.getClass().getClassLoader(), urls, initializers, serviceType); } } catch(Throwable ex) { logCouldNotCreateContribution(ex, elt[i], serviceType); } } } } catch( Throwable t ) { t.printStackTrace(); } // Then load the specific providers, from the current class loader // Change made for WAS environment ApplicationClassLoader ac = adaptApplicationClassloader(loader); if(ac!=null) { // Domino super.findInitializer(loader, initializers, serviceType); } else { // WAS if(loader==Thread.currentThread().getContextClassLoader()) { super.findInitializer(loader, initializers, serviceType); } } } Enumeration<URL> getResourcesList(ClassLoader loader, String serviceType) throws IOException { // In case of a Domino class loader, only return the resources from that class loader ApplicationClassLoader ac = adaptApplicationClassloader(loader); if(ac!=null) { return ac.findApplicationResources(PREFIX+serviceType); } // Else, return a enumeration for all of them return loader.getResources(PREFIX+serviceType); } private void findServicesClassLoader(ClassLoader loader, Set<URL> urls, List<Object> list, String serviceType) throws IOException { // Look for all the resources in the class path final String PREFIX = "META-INF/services/"; for( Enumeration<URL> e=loader.getResources(PREFIX+serviceType); e.hasMoreElements(); ) { URL url = e.nextElement(); findServicesClassLoader(list,loader, urls, url, serviceType); } } private void findServicesClassLoader(List<Object> list, ClassLoader loader, Set<URL> urls, URL url, String serviceType) throws IOException { if(url!=null && (urls==null || !urls.contains(url))) { parseResource(loader,list,url,serviceType); if(urls!=null) { urls.add(url); } } } void logCouldNotCreateContribution(Throwable ex, IConfigurationElement ext, String type) { if( CommonsLogger.STANDARD.isWarnEnabled() ){ String extensionPointId = "com.ibm.commons.Extension"; //$NON-NLS-1$ String className = ext.getAttribute("class"); //$NON-NLS-1$ String warnMsg = "Could not create an instance of {0}, contributed to the extension point {1} with type {2}."; // $NLW-ExtensionManager.Couldnotcreateaninstanceof0contri-1$ CommonsLogger.STANDARD.warnp(this, "logCouldNotCreateContribution", ex, warnMsg, //$NON-NLS-1$ className, extensionPointId, type); } } } // Helper private static ApplicationClassLoader adaptApplicationClassloader(ClassLoader loader) { if(loader instanceof ApplicationClassLoader) { return (ApplicationClassLoader)loader; } return null; } }