| /* 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., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 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.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| 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.LogRecord; |
| import java.util.logging.Logger; |
| |
| |
| /** |
| * 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) |
| { |
| 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(Class, ClassLoader)}. |
| * |
| * @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(Class, ClassLoader)} 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; |
| |
| 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); |
| } |
| } |
| |
| |
| // Package-private to avoid a trampoline. |
| /** |
| * 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 level 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. |
| */ |
| 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); |
| } |
| } |