From 96d22b123242f42c216e90555d07656900e6ae6c Mon Sep 17 00:00:00 2001 From: Michael Koch Date: Wed, 21 Apr 2004 08:20:31 +0000 Subject: ServiceFactory.java, [...]: New files. 2004-04-21 Michael Koch * gnu/classpath/ServiceFactory.java, gnu/classpath/ServiceProviderLoadingAction.java, javax/imageio/ImageReader.java, javax/imageio/ImageTranscoder.java, javax/imageio/ImageWriter.java, javax/imageio/package.html, javax/imageio/spi/IIOServiceProvider.java, javax/imageio/spi/ImageInputStreamSpi.java, javax/imageio/spi/ImageOutputStreamSpi.java, javax/imageio/spi/ImageReaderWriterSpi.java, javax/imageio/spi/ImageTranscoderSpi.java, javax/imageio/spi/RegisterableService.java, javax/imageio/spi/ServiceRegistry.java, javax/imageio/spi/package.html, javax/imageio/stream/IIOByteBuffer.java, javax/imageio/stream/ImageInputStream.java, javax/imageio/stream/ImageOutputStream.java, javax/imageio/stream/package.html: New files. * Makefile.am (ordinary_java_source_files): Added gnu/classpath/ServiceFactory.java and gnu/classpath/ServiceProviderLoadingAction.java. (javax_source_files): Added javax/imageio/ImageReader.java, javax/imageio/ImageTranscoder.java, javax/imageio/ImageWriter.java, javax/imageio/spi/IIOServiceProvider.java, javax/imageio/spi/ImageInputStreamSpi.java, javax/imageio/spi/ImageOutputStreamSpi.java, javax/imageio/spi/ImageReaderWriterSpi.java, javax/imageio/spi/ImageTranscoderSpi.java, javax/imageio/spi/RegisterableService.java, javax/imageio/spi/ServiceRegistry.java, javax/imageio/stream/IIOByteBuffer.java, javax/imageio/stream/ImageInputStream.java and javax/imageio/stream/ImageOutputStream.java. * Makefile.in: Regenerated. From-SVN: r80951 --- libjava/gnu/classpath/ServiceFactory.java | 576 +++++++++++++++++++++ .../classpath/ServiceProviderLoadingAction.java | 149 ++++++ 2 files changed, 725 insertions(+) create mode 100644 libjava/gnu/classpath/ServiceFactory.java create mode 100644 libjava/gnu/classpath/ServiceProviderLoadingAction.java (limited to 'libjava/gnu') diff --git a/libjava/gnu/classpath/ServiceFactory.java b/libjava/gnu/classpath/ServiceFactory.java new file mode 100644 index 0000000..0e81cf7 --- /dev/null +++ b/libjava/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. + * + *

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. + * + *

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 META-INF/services. 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. + * + *

Example + * + *

For example, a JAR might provide two implementations of the + * service provider interface org.foo.ThinkService, + * namely com.acme.QuickThinker and + * com.acme.DeepThinker. The code for QuickThinker + * woud look as follows: + * + *

+ * 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;
+ *   }
+ * }
+ * 
+ * + *

The code for com.acme.DeepThinker is left as an + * exercise to the reader. + * + *

Acme’s ThinkService plug-in gets deployed as + * a JAR file. Besides the bytecode and resources for + * QuickThinker and DeepThinker, it also + * contains the text file + * META-INF/services/org.foo.ThinkService: + * + *

+ * # Available implementations of org.foo.ThinkService
+ * com.acme.QuickThinker
+ * com.acme.DeepThinker
+ * 
+ * + *

Thread Safety + * + *

It is safe to use ServiceFactory from multiple + * concurrent threads without external synchronization. + * + *

Note for User Applications + * + *

User applications that want to load plug-ins should not directly + * use gnu.classpath.ServiceFactory, 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 Sascha Brawer + */ +public final class ServiceFactory +{ + /** + * A logger that gets informed when a service gets loaded, or + * when there is a problem with loading a service. + * + *

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. + * + *

On-demand loading: 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. + * + *

Security considerations: Any loaded service providers + * are loaded through the specified ClassLoader, or the system + * ClassLoader if classLoader is + * null. When lookupProviders is called, + * the current {@link AccessControlContext} gets recorded. This + * captured security context will determine the permissions when + * services get loaded via the next() method of the + * returned Iterator. + * + * @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 null for the system class + * loader. For using the context class loader, see {@link + * #lookupProviders(Class)}. + * + * @return an iterator over instances of spi. + * + * @throws IllegalArgumentException if spi is + * null. + */ + 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 spi. + * + * @throws IllegalArgumentException if spi is + * null. + * + * @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 Sascha Brawer + */ + 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 over the URLs that contain a resource + * META-INF/services/<org.foo.SomeService>, + * 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 null 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 null 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 over the URLs that contain a + * resource + * META-INF/services/<org.foo.SomeService>, 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 + * false. + */ + 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 java.util.logging + * 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 “Could not + * load {0}.” + * + * @param param the parameter(s) for the log message, or + * null if msg does not specify any + * parameters. If param is not an array, an array with + * param as its single element gets passed to the + * logging framework. + * + * @param t a Throwable that is associated with the log record, or + * null 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/libjava/gnu/classpath/ServiceProviderLoadingAction.java b/libjava/gnu/classpath/ServiceProviderLoadingAction.java new file mode 100644 index 0000000..4832c97 --- /dev/null +++ b/libjava/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. + * + *

Class loading and instantiation is encapsulated in a + * PriviledgedAction 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 Sascha Brawer + */ +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 spi, + * providerName or loader is + * null. + */ + 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 + * providerName to the constructor. + * + * @throws ClassCastException if the service provider does not + * implement the spi interface that was passed to the + * constructor. + * + * @throws IllegalAccessException if the service provider class or + * its no-argument constructor are not public. + * + * @throws InstantiationException if the service provider class is + * abstract, 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; + } +} -- cgit v1.1