Skip to content

Instantly share code, notes, and snippets.

@pmuir
Created August 20, 2010 16:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmuir/540594 to your computer and use it in GitHub Desktop.
Save pmuir/540594 to your computer and use it in GitHub Desktop.
/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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 org.jboss.weld.extensions.util.service;
import static java.util.logging.Level.WARNING;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* This class handles looking up service providers on the class path. It
* implements the <a href="http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider"
* >Service Provider section of the JAR File Specification</a>.
*
* The Service Provider programmatic lookup was not specified prior to Java 6 so
* this interface allows use of the specification prior to Java 6.
*
* The API is copied from <a
* href="http://java.sun.com/javase/6/docs/api/java/util/ServiceLoader.html"
* >java.util.ServiceLoader</a>
*
* @author Pete Muir
* @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
* @author Nicklas Karlsson
*/
public class ServiceLoader<S> implements Iterable<S>
{
private static final String SERVICES = "META-INF/services";
private static final Logger log = Logger.getLogger("ServiceLoader");
/**
* Creates a new service loader for the given service type, using the current
* thread's context class loader.
*
* An invocation of this convenience method of the form
*
* {@code ServiceLoader.load(service)</code>}
*
* is equivalent to
*
* <code>ServiceLoader.load(service,
* Thread.currentThread().getContextClassLoader())</code>
*
* @param service The interface or abstract class representing the service
* @return A new service loader
*/
public static <S> ServiceLoader<S> load(Class<S> service)
{
return load(service, Thread.currentThread().getContextClassLoader());
}
/**
* Creates a new service loader for the given service type and class loader.
*
* @param service The interface or abstract class representing the service
* @param loader The class loader to be used to load provider-configuration
* files and provider classes, or null if the system class loader
* (or, failing that, the bootstrap class loader) is to be used
* @return A new service loader
*/
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader)
{
if (loader == null)
{
loader = service.getClassLoader();
}
return new ServiceLoader<S>(service, loader);
}
/**
* Creates a new service loader for the given service type, using the
* extension class loader.
*
* This convenience method simply locates the extension class loader, call it
* extClassLoader, and then returns
*
* <code>ServiceLoader.load(service, extClassLoader)</code>
*
* If the extension class loader cannot be found then the system class loader
* is used; if there is no system class loader then the bootstrap class
* loader is used.
*
* This method is intended for use when only installed providers are desired.
* The resulting service will only find and load providers that have been
* installed into the current Java virtual machine; providers on the
* application's class path will be ignored.
*
* @param service The interface or abstract class representing the service
* @return A new service loader
*/
public static <S> ServiceLoader<S> loadInstalled(Class<S> service)
{
throw new UnsupportedOperationException("Not implemented");
}
private final String serviceFile;
private Class<S> expectedType;
private final ClassLoader loader;
private Set<S> providers;
private ServiceLoader(Class<S> service, ClassLoader loader)
{
this.loader = loader;
this.serviceFile = SERVICES + "/" + service.getName();
this.expectedType = service;
}
/**
* Clear this loader's provider cache so that all providers will be reloaded.
*
* After invoking this method, subsequent invocations of the iterator method
* will lazily look up and instantiate providers from scratch, just as is
* done by a newly-created loader.
*
* This method is intended for use in situations in which new providers can
* be installed into a running Java virtual machine.
*/
public void reload()
{
providers = new HashSet<S>();
for (URL serviceFile : loadServiceFiles())
{
loadServiceFile(serviceFile);
}
}
private List<URL> loadServiceFiles()
{
List<URL> serviceFiles = new ArrayList<URL>();
try
{
Enumeration<URL> serviceFileEnumerator = loader.getResources(serviceFile);
while (serviceFileEnumerator.hasMoreElements())
{
serviceFiles.add(serviceFileEnumerator.nextElement());
}
}
catch (IOException e)
{
throw new RuntimeException("Could not load resources from " + serviceFile, e);
}
return serviceFiles;
}
private void loadServiceFile(URL serviceFile)
{
InputStream is = null;
try
{
is = serviceFile.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String serviceClassName = null;
while ((serviceClassName = reader.readLine()) != null)
{
serviceClassName = trim(serviceClassName);
if (serviceClassName.length() > 0)
{
loadService(serviceClassName);
}
}
}
catch (IOException e)
{
// FIXME: correct exception
throw new RuntimeException("Could not read services file " + serviceFile);
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (IOException e)
{
// FIXME: correct exception
throw new RuntimeException("Could not close services file " + serviceFile, e);
}
}
}
}
private String trim(String line)
{
final int comment = line.indexOf('#');
if (comment > -1)
{
line = line.substring(0, comment);
}
return line.trim();
}
private void loadService(String serviceClassName)
{
Class<? extends S> serviceClass = loadClass(serviceClassName);
if (serviceClass == null)
{
return;
}
S serviceInstance = prepareInstance(serviceClass);
if (serviceInstance == null)
{
return;
}
providers.add(serviceInstance);
}
private Class<? extends S> loadClass(String serviceClassName)
{
Class<?> clazz = null;
Class<? extends S> serviceClass = null;
try
{
clazz = loader.loadClass(serviceClassName);
serviceClass = clazz.asSubclass(expectedType);
}
catch (ClassNotFoundException e)
{
log.warning("Could not load service class " + serviceClassName);
}
catch (ClassCastException e)
{
throw new RuntimeException("Service class " + serviceClassName + " didn't implement the Extension interface");
}
return serviceClass;
}
private S prepareInstance(Class<? extends S> serviceClass)
{
try
{
// TODO Support the SM
Constructor<? extends S> constructor = serviceClass.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
}
catch (NoClassDefFoundError e)
{
log.log(WARNING, "Could not instantiate service class " + serviceClass.getName(), e);
return null;
}
catch (InvocationTargetException e)
{
throw new RuntimeException("Error instantiating " + serviceClass, e.getCause());
}
catch (IllegalArgumentException e)
{
throw new RuntimeException("Error instantiating " + serviceClass, e);
}
catch (InstantiationException e)
{
throw new RuntimeException("Error instantiating " + serviceClass, e);
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Error instantiating " + serviceClass, e);
}
catch (SecurityException e)
{
throw new RuntimeException("Error instantiating " + serviceClass, e);
}
catch (NoSuchMethodException e)
{
throw new RuntimeException("Error instantiating " + serviceClass, e);
}
}
/**
* Lazily loads the available providers of this loader's service.
*
* The iterator returned by this method first yields all of the elements of
* the provider cache, in instantiation order. It then lazily loads and
* instantiates any remaining providers, adding each one to the cache in
* turn.
*
* To achieve laziness the actual work of parsing the available
* provider-configuration files and instantiating providers must be done by
* the iterator itself. Its hasNext and next methods can therefore throw a
* ServiceConfigurationError if a provider-configuration file violates the
* specified format, or if it names a provider class that cannot be found and
* instantiated, or if the result of instantiating the class is not
* assignable to the service type, or if any other kind of exception or error
* is thrown as the next provider is located and instantiated. To write
* robust code it is only necessary to catch ServiceConfigurationError when
* using a service iterator.
*
* If such an error is thrown then subsequent invocations of the iterator
* will make a best effort to locate and instantiate the next available
* provider, but in general such recovery cannot be guaranteed.
*
* Design Note Throwing an error in these cases may seem extreme. The
* rationale for this behavior is that a malformed provider-configuration
* file, like a malformed class file, indicates a serious problem with the
* way the Java virtual machine is configured or is being used. As such it is
* preferable to throw an error rather than try to recover or, even worse,
* fail silently.
*
* The iterator returned by this method does not support removal. Invoking
* its remove method will cause an UnsupportedOperationException to be
* thrown.
*
* @return An iterator that lazily loads providers for this loader's service
*/
public Iterator<S> iterator()
{
if (providers == null)
{
reload();
}
return providers.iterator();
}
/**
* Returns a string describing this service.
*
* @return A descriptive string
*/
@Override
public String toString()
{
return "Services for " + serviceFile;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment