/*
* JBoss, Home of Professional Open Source
* Copyright 2008 Red Hat Inc. and/or its affiliates and other 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.arquillian.container.test.spi.util;
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.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 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>.
* <p>
* The Service Provider programmatic lookup was not specified prior to Java 6 so
* this interface allows use of the specification prior to Java 6.
* <p>
* 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>
*/
public class ServiceLoader<S> implements Iterable<S> {
private static final String SERVICES = "META-INF/services";
private final String serviceFile;
private final ClassLoader loader;
private Class<S> expectedType;
private Set<S> providers;
private ServiceLoader(Class<S> service, ClassLoader loader) {
this.loader = loader;
this.serviceFile = SERVICES + "/" + service.getName();
this.expectedType = service;
}
/**
* Creates a new service loader for the given service type, using the current
* thread's context class loader.
* <p>
* An invocation of this convenience method of the form
* <p>
* {@code ServiceLoader.load(service)</code>}
* <p>
* is equivalent to
* <p>
* <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.
* <p>
* This convenience method simply locates the extension class loader, call it
* extClassLoader, and then returns
* <p>
* <code>ServiceLoader.load(service, extClassLoader)</code>
* <p>
* 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.
* <p>
* 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();
}
/**
* Clear this loader's provider cache so that all providers will be reloaded.
* <p>
* 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.
* <p>
* 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 LinkedHashSet<S>();
Enumeration<URL> enumeration = null;
boolean errorOccurred = false;
try {
enumeration = loader.getResources(serviceFile);
} catch (IOException ioe) {
errorOccurred = true;
}
if (!errorOccurred) {
while (enumeration.hasMoreElements()) {
try {
final URL url = enumeration.nextElement();
final InputStream is = url.openStream();
final BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String line = reader.readLine();
while (null != line) {
try {
final int comment = line.indexOf('#');
if (comment > -1) {
line = line.substring(0, comment);
}
line.trim();
if (line.length() > 0) {
providers.add(createInstance(line));
}
} catch (Exception e) {
e.printStackTrace();
// TODO Don't use exceptions for flow control!
// try the next line
}
line = reader.readLine();
}
reader.close();
} catch (Exception e) {
// try the next file
}
}
}
}
public S createInstance(String line) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
NoClassDefFoundError, InstantiationException, IllegalAccessException {
try {
Class<?> clazz = loader.loadClass(line);
Class<? extends S> serviceClass;
try {
serviceClass = clazz.asSubclass(expectedType);
} catch (ClassCastException e) {
throw new IllegalStateException(
"Service " + line + " does not implement expected type " + expectedType.getName());
}
Constructor<? extends S> constructor = serviceClass.getConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance();
} catch (NoClassDefFoundError e) {
throw e;
} catch (InstantiationException e) {
throw e;
} catch (IllegalAccessException e) {
throw e;
}
}
/**
* Lazily loads the available providers of this loader's service.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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();
}
public Set<S> getProviders() {
if (providers == null) {
reload();
}
return Collections.unmodifiableSet(providers);
}
/**
* Returns a string describing this service.
*
* @return A descriptive string
*/
@Override
public String toString() {
return "Services for " + serviceFile;
}
}