diff options
author | Sascha Brawer <brawer@dandelis.ch> | 2004-03-24 07:29:56 +0000 |
---|---|---|
committer | Sascha Brawer <brawer@dandelis.ch> | 2004-03-24 07:29:56 +0000 |
commit | a96cff5ca005c87ad96abf8233642d396f91f1b3 (patch) | |
tree | 55c940d9c77adff2b56753f9f893137264946669 /gnu/classpath | |
parent | 2fd21783dd1ace50ace9314423087ab9840348d5 (diff) | |
download | classpath-a96cff5ca005c87ad96abf8233642d396f91f1b3.tar.gz |
Facility for loading plug-in services specified in META-INF/services resources.
Diffstat (limited to 'gnu/classpath')
-rw-r--r-- | gnu/classpath/ServiceFactory.java | 576 | ||||
-rw-r--r-- | gnu/classpath/ServiceProviderLoadingAction.java | 149 |
2 files changed, 725 insertions, 0 deletions
diff --git a/gnu/classpath/ServiceFactory.java b/gnu/classpath/ServiceFactory.java new file mode 100644 index 000000000..0e81cf74b --- /dev/null +++ b/gnu/classpath/ServiceFactory.java @@ -0,0 +1,576 @@ +/* ServiceFactory.java -- Factory for plug-in services. + Copyright (C) 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.classpath; + +import java.io.InputStream; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.LogRecord; + + +/** + * A factory for plug-ins that conform to a service provider + * interface. This is a general mechanism that gets used by a number + * of packages in the Java API. For instance, {@link + * java.nio.charset.spi.CharsetProvider} allows to write custom + * encoders and decoders for character sets, {@link + * javax.imageio.spi.ImageReaderSpi} allows to support custom image + * formats, and {@link javax.print.PrintService} makes it possible to + * write custom printer drivers. + * + * <p>The plug-ins are concrete implementations of the service + * provider interface, which is defined as an interface or an abstract + * class. The implementation classes must be public and have a public + * constructor that takes no arguments. + * + * <p>Plug-ins are usually deployed in JAR files. A JAR that provides + * an implementation of a service must declare this in a resource file + * whose name is the fully qualified service name and whose location + * is the directory <code>META-INF/services</code>. This UTF-8 encoded + * text file lists, on separate lines, the fully qualified names of + * the concrete implementations. Thus, one JAR file can provide an + * arbitrary number of implementations for an arbitrary count of + * service provider interfaces. + * + * <p><b>Example</b> + * + * <p>For example, a JAR might provide two implementations of the + * service provider interface <code>org.foo.ThinkService</code>, + * namely <code>com.acme.QuickThinker</code> and + * <code>com.acme.DeepThinker</code>. The code for <code>QuickThinker</code> + * woud look as follows: + * + * <pre> + * package com.acme; + * + * /** + * * Provices a super-quick, but not very deep implementation of ThinkService. + * */ + * public class QuickThinker + * implements org.foo.ThinkService + * { + * /** + * * Constructs a new QuickThinker. The service factory (which is + * * part of the Java environment) calls this no-argument constructor + * * when it looks up the available implementations of ThinkService. + * * + * * <p>Note that an application might query all available + * * ThinkService providers, but use just one of them. Therefore, + * * constructing an instance should be very inexpensive. For example, + * * large data structures should only be allocated when the service + * * actually gets used. + * */ + * public QuickThinker() + * { + * } + * + * /** + * * Returns the speed of this ThinkService in thoughts per second. + * * Applications can choose among the available service providers + * * based on this value. + * */ + * public double getSpeed() + * { + * return 314159.2654; + * } + * + * /** + * * Produces a thought. While the returned thoughts are not very + * * deep, they are generated in very short time. + * */ + * public Thought think() + * { + * return null; + * } + * } + * </pre> + * + * <p>The code for <code>com.acme.DeepThinker</code> is left as an + * exercise to the reader. + * + * <p>Acme’s <code>ThinkService</code> plug-in gets deployed as + * a JAR file. Besides the bytecode and resources for + * <code>QuickThinker</code> and <code>DeepThinker</code>, it also + * contains the text file + * <code>META-INF/services/org.foo.ThinkService</code>: + * + * <pre> + * # Available implementations of org.foo.ThinkService + * com.acme.QuickThinker + * com.acme.DeepThinker + * </pre> + * + * <p><b>Thread Safety</b> + * + * <p>It is safe to use <code>ServiceFactory</code> from multiple + * concurrent threads without external synchronization. + * + * <p><b>Note for User Applications</b> + * + * <p>User applications that want to load plug-ins should not directly + * use <code>gnu.classpath.ServiceFactory</code>, because this class + * is only available in Java environments that are based on GNU + * Classpath. Instead, it is recommended that user applications call + * {@link + * javax.imageio.spi.ServiceRegistry#lookupProviders(Class)}. This API + * is actually independent of image I/O, and it is available on every + * environment. + * + * @author <a href="mailto:brawer@dandelis.ch">Sascha Brawer</a> + */ +public final class ServiceFactory +{ + /** + * A logger that gets informed when a service gets loaded, or + * when there is a problem with loading a service. + * + * <p>Because {@link java.util.logging.Logger#getLogger(String)} + * is thread-safe, we do not need to worry about synchronization + * here. + */ + private static final Logger LOGGER = Logger.getLogger("gnu.classpath"); + + + /** + * Declared private in order to prevent constructing instances of + * this utility class. + */ + private ServiceFactory() + { + } + + + /** + * Finds service providers that are implementing the specified + * Service Provider Interface. + * + * <p><b>On-demand loading:</b> Loading and initializing service + * providers is delayed as much as possible. The rationale is that + * typical clients will iterate through the set of installed service + * providers until one is found that matches some criteria (like + * supported formats, or quality of service). In such scenarios, it + * might make sense to install only the frequently needed service + * providers on the local machine. More exotic providers can be put + * onto a server; the server will only be contacted when no suitable + * service could be found locally. + * + * <p><b>Security considerations:</b> Any loaded service providers + * are loaded through the specified ClassLoader, or the system + * ClassLoader if <code>classLoader</code> is + * <code>null</code>. When <code>lookupProviders</code> is called, + * the current {@link AccessControlContext} gets recorded. This + * captured security context will determine the permissions when + * services get loaded via the <code>next()</code> method of the + * returned <code>Iterator</code>. + * + * @param spi the service provider interface which must be + * implemented by any loaded service providers. + * + * @param loader the class loader that will be used to load the + * service providers, or <code>null</code> for the system class + * loader. For using the context class loader, see {@link + * #lookupProviders(Class)}. + * + * @return an iterator over instances of <code>spi</code>. + * + * @throws IllegalArgumentException if <code>spi</code> is + * <code>null</code>. + */ + public static Iterator lookupProviders(Class spi, + ClassLoader loader) + { + InputStream stream; + String resourceName; + Enumeration urls; + + if (spi == null) + throw new IllegalArgumentException(); + + if (loader == null) + loader = ClassLoader.getSystemClassLoader(); + + resourceName = "META-INF/services/" + spi.getName(); + try + { + urls = loader.getResources(resourceName); + } + catch (IOException ioex) + { + /* If an I/O error occurs here, we cannot provide any service + * providers. In this case, we simply return an iterator that + * does not return anything (no providers installed). + */ + log(Level.WARNING, "cannot access {0}", resourceName, ioex); + return Collections.EMPTY_LIST.iterator(); + } + + return new ServiceIterator(spi, urls, loader, + AccessController.getContext()); + } + + + /** + * Finds service providers that are implementing the specified + * Service Provider Interface, using the context class loader + * for loading providers. + * + * @param spi the service provider interface which must be + * implemented by any loaded service providers. + * + * @return an iterator over instances of <code>spi</code>. + * + * @throws IllegalArgumentException if <code>spi</code> is + * <code>null</code>. + * + * @see #lookupProviders(Class, ClassLoader) + */ + public static Iterator lookupProviders(Class spi) + { + ClassLoader ctxLoader; + + ctxLoader = Thread.currentThread().getContextClassLoader(); + return lookupProviders(spi, ctxLoader); + } + + + /** + * An iterator over service providers that are listed in service + * provider configuration files, which get passed as an Enumeration + * of URLs. This is a helper class for {@link + * ServiceFactory#lookupProviders}. + * + * @author <a href="mailto:brawer@dandelis.ch">Sascha Brawer</a> + */ + private static final class ServiceIterator + implements Iterator + { + /** + * The service provider interface (usually an interface, sometimes + * an abstract class) which the services must implement. + */ + private final Class spi; + + + /** + * An Enumeration<URL> over the URLs that contain a resource + * <code>META-INF/services/<org.foo.SomeService></code>, + * as returned by {@link ClassLoader#getResources(String)}. + */ + private final Enumeration urls; + + + /** + * The class loader used for loading service providers. + */ + private final ClassLoader loader; + + + /** + * The security context used when loading and initializing service + * providers. We want to load and initialize all plug-in service + * providers under the same security context, namely the one that + * was active when {@link #lookupProviders} has been called. + */ + private final AccessControlContext securityContext; + + + /** + * A reader for the current file listing class names of service + * implementors, or <code>null</code> when the last reader has + * been fetched. + */ + private BufferedReader reader; + + + /** + * The URL currently being processed. This is only used for + * emitting error messages. + */ + private URL currentURL; + + + /** + * The service provider that will be returned by the next call to + * {@link #next()}, or <code>null</code> if the iterator has + * already returned all service providers. + */ + private Object nextProvider; + + + /** + * Constructs an Iterator that loads and initializes services on + * demand. + * + * @param spi the service provider interface which the services + * must implement. Usually, this is a Java interface type, but it + * might also be an abstract class or even a concrete superclass. + * + * @param urls an Enumeration<URL> over the URLs that contain a + * resource + * <code>META-INF/services/<org.foo.SomeService></code>, as + * determined by {@link ClassLoader#getResources(String)}. + * + * @param loader the ClassLoader that gets used for loading + * service providers. + * + * @param securityContext the security context to use when loading + * and initializing service providers. + */ + ServiceIterator(Class spi, Enumeration urls, ClassLoader loader, + AccessControlContext securityContext) + { + this.spi = spi; + this.urls = urls; + this.loader = loader; + this.securityContext = securityContext; + this.nextProvider = loadNextServiceProvider(); + } + + + /** + * @throws NoSuchElementException if {@link #hasNext} returns + * <code>false</code>. + */ + public Object next() + { + Object result; + + if (!hasNext()) + throw new NoSuchElementException(); + + result = nextProvider; + nextProvider = loadNextServiceProvider(); + return result; + } + + + public boolean hasNext() + { + return nextProvider != null; + } + + + public void remove() + { + throw new UnsupportedOperationException(); + } + + + private Object loadNextServiceProvider() + { + String line; + Class klass; + + if (reader == null) + advanceReader(); + + for (;;) + { + /* If we have reached the last provider list, we cannot + * retrieve any further lines. + */ + if (reader == null) + return null; + + try + { + line = reader.readLine(); + } + catch (IOException readProblem) + { + log(Level.WARNING, "IOException upon reading {0}", currentURL, + readProblem); + line = null; + } + + /* When we are at the end of one list of services, + * switch over to the next one. + */ + if (line == null) + { + advanceReader(); + continue; + } + + + // Skip whitespace at the beginning and end of each line. + line = line.trim(); + + // Skip empty lines. + if (line.length() == 0) + continue; + + // Skip comment lines. + if (line.charAt(0) == '#') + continue; + + try + { + log(Level.FINE, + "Loading service provider \"{0}\", specified" + + " by \"META-INF/services/{1}\" in {2}.", + new Object[] { line, spi.getName(), currentURL }, + null); + + /* Load the class in the security context that was + * active when calling lookupProviders. + */ + return AccessController.doPrivileged( + new ServiceProviderLoadingAction(spi, line, loader), + securityContext); + } + catch (Exception ex) + { + String msg = "Cannot load service provider class \"{0}\"," + + " specified by \"META-INF/services/{1}\" in {2}"; + if (ex instanceof PrivilegedActionException + && ex.getCause() instanceof ClassCastException) + msg = "Service provider class \"{0}\" is not an instance" + + " of \"{1}\". Specified" + + " by \"META-INF/services/{1}\" in {2}."; + + log(Level.WARNING, msg, + new Object[] { line, spi.getName(), currentURL }, + ex); + continue; + } + } + } + + + private void advanceReader() + { + do + { + if (reader != null) + { + try + { + reader.close(); + log(Level.FINE, "closed {0}", currentURL, null); + } + catch (Exception ex) + { + log(Level.WARNING, "cannot close {0}", currentURL, ex); + } + reader = null; + currentURL = null; + } + + if (!urls.hasMoreElements()) + return; + + currentURL = (URL) urls.nextElement(); + try + { + reader = new BufferedReader(new InputStreamReader( + currentURL.openStream(), "UTF-8")); + log(Level.FINE, "opened {0}", currentURL, null); + } + catch (Exception ex) + { + log(Level.WARNING, "cannot open {0}", currentURL, ex); + } + } + while (reader == null); + } + } + + + /** + * Passes a log message to the <code>java.util.logging</code> + * framework. This call returns very quickly if no log message will + * be produced, so there is not much overhead in the standard case. + * + * @param the severity of the message, for instance {@link + * Level#WARNING}. + * + * @param msg the log message, for instance <code>“Could not + * load {0}.”</code> + * + * @param param the parameter(s) for the log message, or + * <code>null</code> if <code>msg</code> does not specify any + * parameters. If <code>param</code> is not an array, an array with + * <code>param</code> as its single element gets passed to the + * logging framework. + * + * @param t a Throwable that is associated with the log record, or + * <code>null</code> if the log message is not associated with a + * Throwable. + */ + private static void log(Level level, String msg, Object param, Throwable t) + { + LogRecord rec; + + // Return quickly if no log message will be produced. + if (!LOGGER.isLoggable(level)) + return; + + rec = new LogRecord(level, msg); + if (param != null && param.getClass().isArray()) + rec.setParameters((Object[]) param); + else + rec.setParameters(new Object[] { param }); + + rec.setThrown(t); + + // While java.util.logging can sometimes infer the class and + // method of the caller, this automatic inference is not reliable + // on highly optimizing VMs. Also, log messages make more sense to + // developers when they display a public method in a public class; + // otherwise, they might feel tempted to figure out the internals + // of ServiceFactory in order to understand the problem. + rec.setSourceClassName(ServiceFactory.class.getName()); + rec.setSourceMethodName("lookupProviders"); + + LOGGER.log(rec); + } +} diff --git a/gnu/classpath/ServiceProviderLoadingAction.java b/gnu/classpath/ServiceProviderLoadingAction.java new file mode 100644 index 000000000..4832c9712 --- /dev/null +++ b/gnu/classpath/ServiceProviderLoadingAction.java @@ -0,0 +1,149 @@ +/* ServiceProviderLoadingAction.java -- Action for loading plug-in services. + Copyright (C) 2004 Free Software Foundation + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.classpath; + +import java.security.PrivilegedExceptionAction; + +/** + * A privileged action for creating a new instance of a service + * provider. + * + * <p>Class loading and instantiation is encapsulated in a + * <code>PriviledgedAction</code> in order to restrict the loaded + * service providers to the {@link java.security.AccessControlContext} + * that was active when {@link + * gnu.classpath.ServiceFactory#lookupProviders} was called, even + * though the actual loading is delayed to the time when the provider + * is actually needed. + * + * @author <a href="mailto:brawer@dandelis.ch">Sascha Brawer</a> + */ +final class ServiceProviderLoadingAction + implements PrivilegedExceptionAction +{ + /** + * The interface to which the loaded service provider implementation + * must conform. Usually, this is a Java interface type, but it + * might also be an abstract class or even a concrete class. + */ + private final Class spi; + + + /** + * The fully qualified name of the class that gets loaded when + * this action is executed. + */ + private final String providerName; + + + /** + * The ClassLoader that gets used for loading the service provider + * class. + */ + private final ClassLoader loader; + + + /** + * Constructs a privileged action for loading a service provider. + * + * @param spi the interface to which the loaded service provider + * implementation must conform. Usually, this is a Java interface + * type, but it might also be an abstract class or even a concrete + * superclass. + * + * @param providerName the fully qualified name of the class that + * gets loaded when this action is executed. + * + * @param loader the ClassLoader that gets used for loading the + * service provider class. + * + * @throws IllegalArgumentException if <code>spi</code>, + * <code>providerName</code> or <code>loader</code> is + * <code>null</code>. + */ + ServiceProviderLoadingAction(Class spi, String providerName, + ClassLoader loader) + { + if (spi == null || providerName == null || loader == null) + throw new IllegalArgumentException(); + + this.spi = spi; + this.providerName = providerName; + this.loader = loader; + } + + + /** + * Loads an implementation class for a service provider, and creates + * a new instance of the loaded class by invoking its public + * no-argument constructor. + * + * @return a new instance of the class whose name was passed as + * <code>providerName</code> to the constructor. + * + * @throws ClassCastException if the service provider does not + * implement the <code>spi</code> interface that was passed to the + * constructor. + * + * @throws IllegalAccessException if the service provider class or + * its no-argument constructor are not <code>public</code>. + * + * @throws InstantiationException if the service provider class is + * <code>abstract</code>, an interface, a primitive type, an array + * class, or void; or if service provider class does not have a + * no-argument constructor; or if there some other problem with + * creating a new instance of the service provider. + */ + public Object run() + throws Exception + { + Class loadedClass; + Object serviceProvider; + + loadedClass = loader.loadClass(providerName); + serviceProvider = loadedClass.newInstance(); + + // Ensure that the loaded provider is actually implementing + // the service provider interface. + if (!spi.isInstance(serviceProvider)) + throw new ClassCastException(spi.getName()); + + return serviceProvider; + } +} |