
org.jboss.shrinkwrap.resolver.spi.loader.SpiServiceLoader Maven / Gradle / Ivy
Show all versions of shrinkwrap-resolver-spi Show documentation
/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat Middleware LLC, 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.shrinkwrap.resolver.spi.loader;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A basic {@link ServiceLoader} implementation which uses META-INF/services registration.
*
* In order to register a service, create a file META-INF/services/${service.interface.name}. The content of the file should
* list fully qualified names of interface implementations, separated by new line character.
*
* @author Aslak Knutsen
* @author Karel Piwko
* @author ALR
*/
public class SpiServiceLoader implements ServiceLoader {
private static final Logger log = Logger.getLogger(SpiServiceLoader.class.getName());
// -------------------------------------------------------------------------------------||
// Class Members ----------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
private static final String SERVICES = "META-INF/services";
private ClassLoader classLoader;
/**
* Create an instance of SPI service loader
*/
public SpiServiceLoader() {
// Use the CL which loaded this class as a default
this.classLoader = SpiServiceLoader.class.getClassLoader();
}
/**
* Creates an instance of SPI service loader. Uses specific {@link ClassLoader} to load service implementations.
*
* @param classLoader The {@link ClassLoader} to be used for loading service implementations
*/
public SpiServiceLoader(final ClassLoader classLoader) {
this.classLoader = classLoader;
}
// -------------------------------------------------------------------------------------||
// Required Implementations - ServiceLoader --------------------------------------------||
// -------------------------------------------------------------------------------------||
@Override
public Collection all(Class serviceClass) {
if (serviceClass == null) {
throw new IllegalArgumentException("ServiceClass must be provided");
}
return createInstances(serviceClass, load(serviceClass));
}
@Override
public T onlyOne(Class serviceClass) {
Collection services = all(serviceClass);
if (services.isEmpty()) {
throw new IllegalStateException("There are no services for serviceClass " + serviceClass.getName());
}
else if (services.size() > 1) {
throw new IllegalStateException("There are more than 1 services for serviceClass " + serviceClass.getName());
}
return services.iterator().next();
}
@Override
public T onlyOne(Class serviceClass, Class extends T> defaultImplementationClass) {
if (defaultImplementationClass == null) {
throw new IllegalArgumentException("DefaultImplementationClass must be provided");
}
Collection services = all(serviceClass);
if (services.isEmpty()) {
return createInstance(defaultImplementationClass);
} else if (services.size() == 1) {
return services.iterator().next();
} else if (services.size() == 2) {
for (T service : services) {
if (defaultImplementationClass.equals(service.getClass())) {
continue;
}
return service;
}
}
throw new IllegalStateException("There is more then a one service for serviceClass " + serviceClass.getName());
}
// -------------------------------------------------------------------------------------||
// Getters and setters -----------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
// -------------------------------------------------------------------------------------||
// Internal Helper Methods - Service Loading -------------------------------------------||
// -------------------------------------------------------------------------------------||
private Set> load(Class serviceClass) {
String serviceFile = SERVICES + "/" + serviceClass.getName();
LinkedHashSet> providers = new LinkedHashSet<>();
try {
Enumeration enumeration = classLoader.getResources(serviceFile);
while (enumeration.hasMoreElements()) {
final URL url = enumeration.nextElement();
final InputStream is = url.openStream();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line = reader.readLine();
while (null != line) {
line = skipCommentAndTrim(line);
if (!line.isEmpty()) {
try {
Class extends T> provider = classLoader.loadClass(line).asSubclass(serviceClass);
providers.add(provider);
} catch (ClassCastException e) {
ClassLoader other = serviceClass.getClassLoader();
if (!classLoader.getClass().equals(serviceClass.getClassLoader())) {
throw new IllegalStateException("Service " + line
+ " was loaded by different classloader (" + (other == null ? "bootstrap"
: other.getClass().getName()) + ") then service interface "
+ serviceClass.getName() + " (" + classLoader.getClass().getName()
+ "), unable to cast classes");
}
throw new IllegalStateException("Service " + line + " does not implement expected type "
+ serviceClass.getName());
}
}
line = reader.readLine();
}
}
}
} catch (Exception e) {
throw new RuntimeException("Could not load services for " + serviceClass.getName(), e);
}
return providers;
}
private String skipCommentAndTrim(String line) {
final int comment = line.indexOf('#');
if (comment > -1) {
line = line.substring(0, comment);
}
line = line.trim();
return line;
}
private Set createInstances(Class serviceType, Set> providers) {
Set providerImpls = new LinkedHashSet<>();
for (Class extends T> serviceClass : providers) {
// support enums as possible service providers
if (serviceClass.isEnum()) {
T[] enumInstances = serviceClass.getEnumConstants();
for (T enumInstance : enumInstances) {
providerImpls.add(enumInstance);
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Registered new service for type {0}: {1}#{2}", new Object[] {
serviceType.getName(), serviceClass.getName(), enumInstance.toString() });
}
}
} else {
// add classes as service providers
providerImpls.add(createInstance(serviceClass));
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Registered new service for type {0}: {1}", new Object[] { serviceType.getName(),
serviceClass.getName() });
}
}
}
return providerImpls;
}
/**
* Create a new instance of the found Service.
*
* Verifies that the found ServiceImpl implements Service.
*
* @param the type of the service interface
* @param implClass the implementation class to instantiate
* @return A new instance of the ServiceImpl
*/
private T createInstance(final Class implClass) {
{
// Get the constructor to use in making the new instance
final Constructor extends T> ctor;
try {
ctor = SecurityActions.getConstructor(implClass);
} catch (final NoSuchMethodException nsme) {
throw new RuntimeException(implClass + " must contain a public no args constructor");
}
// Create a new instance using the backing model
final T instance;
try {
instance = ctor.newInstance();
}
// Handle all construction errors equally
catch (final Exception e) {
throw new RuntimeException("Could not create new service instance", e);
}
// Return
return implClass.cast(instance);
}
}
}