summaryrefslogtreecommitdiff
path: root/gnu/classpath
diff options
context:
space:
mode:
authorSascha Brawer <brawer@dandelis.ch>2004-03-24 07:29:56 +0000
committerSascha Brawer <brawer@dandelis.ch>2004-03-24 07:29:56 +0000
commita96cff5ca005c87ad96abf8233642d396f91f1b3 (patch)
tree55c940d9c77adff2b56753f9f893137264946669 /gnu/classpath
parent2fd21783dd1ace50ace9314423087ab9840348d5 (diff)
downloadclasspath-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.java576
-rw-r--r--gnu/classpath/ServiceProviderLoadingAction.java149
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;
+ *
+ * &#x2f;**
+ * * Provices a super-quick, but not very deep implementation of ThinkService.
+ * *&#x2f;
+ * public class QuickThinker
+ * implements org.foo.ThinkService
+ * {
+ * &#x2f;**
+ * * 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.
+ * *
+ * * &lt;p&gt;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.
+ * *&#x2f;
+ * public QuickThinker()
+ * {
+ * }
+ *
+ * &#x2f;**
+ * * Returns the speed of this ThinkService in thoughts per second.
+ * * Applications can choose among the available service providers
+ * * based on this value.
+ * *&#x2f;
+ * public double getSpeed()
+ * {
+ * return 314159.2654;
+ * }
+ *
+ * &#x2f;**
+ * * Produces a thought. While the returned thoughts are not very
+ * * deep, they are generated in very short time.
+ * *&#x2f;
+ * 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&#x2019;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/&lt;org.foo.SomeService&gt;</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/&lt;org.foo.SomeService&gt;</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>&#x201c;Could not
+ * load {0}.&#x201d;</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;
+ }
+}