/*
* JBoss, Home of Professional Open Source
* Copyright 2009 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.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import org.jboss.arquillian.container.test.spi.RemoteLoadableExtension;
import org.jboss.arquillian.core.spi.ExtensionLoader;
import org.jboss.arquillian.core.spi.LoadableExtension;
import org.jboss.arquillian.core.spi.Validate;
/**
* ARQ-456 Temp fix. Load a different type of LoadableExtension on the container side, RemoteLoadableExtension.
* <p>
* Should be replaced when proper Modularity, Classloading is in order.
* <p>
* ServiceLoader implementation that use META-INF/services/interface files to registered Services.
*
* @author <a href="mailto:aslak@conduct.no">Aslak Knutsen</a>
* @version $Revision: $
*/
public class RemoteExtensionLoader implements ExtensionLoader {
//-------------------------------------------------------------------------------------||
// Class Members ----------------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
private static final String SERVICES = "META-INF/services";
private static final String EXCLUSIONS = "META-INF/exclusions";
//-------------------------------------------------------------------------------------||
// Required Implementations - ExtensionLoader -----------------------------------------||
//-------------------------------------------------------------------------------------||
@Override
public Collection<LoadableExtension> load() {
List<LoadableExtension> extensions = new ArrayList<LoadableExtension>();
Collection<RemoteLoadableExtension> loaded = Collections.emptyList();
if (SecurityActions.getThreadContextClassLoader() != null) {
loaded = all(SecurityActions.getThreadContextClassLoader(), RemoteLoadableExtension.class);
}
if (loaded.size() == 0) {
loaded = all(RemoteExtensionLoader.class.getClassLoader(), RemoteLoadableExtension.class);
}
for (RemoteLoadableExtension extension : loaded) {
extensions.add(extension);
}
return extensions;
}
@Override
public Map<Class<?>, Set<Class<?>>> loadVetoed() {
return loadVetoed(SecurityActions.getThreadContextClassLoader());
}
//-------------------------------------------------------------------------------------||
// General JDK SPI Loader -------------------------------------------------------------||
//-------------------------------------------------------------------------------------||
private <T> Collection<T> all(ClassLoader classLoader, Class<T> serviceClass) {
Validate.notNull(classLoader, "ClassLoader must be provided");
Validate.notNull(serviceClass, "ServiceClass must be provided");
return createInstances(
serviceClass,
load(serviceClass, classLoader));
}
/**
* This method first finds all files that are in claspath placed at META-INF/exclusions
* Each of this file has a name that represents the service type that needs to veto.
* The content of this file is a list of real implementations that you want to veto.
*
* @return List of vetos
*/
public Map<Class<?>, Set<Class<?>>> loadVetoed(ClassLoader classLoader) {
Validate.notNull(classLoader, "ClassLoader must be provided");
final Map<Class<?>, Set<Class<?>>> vetoed = new LinkedHashMap<Class<?>, Set<Class<?>>>();
try {
final Enumeration<URL> exclusions = classLoader.getResources(EXCLUSIONS);
while (exclusions.hasMoreElements()) {
URL exclusion = exclusions.nextElement();
Properties vetoedElements = new Properties();
final InputStream inStream = exclusion.openStream();
try {
vetoedElements.load(inStream);
final Set<Map.Entry<Object, Object>> entries = vetoedElements.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
String service = (String) entry.getKey();
String serviceImpls = (String) entry.getValue();
addVetoedClasses(service, serviceImpls, classLoader, vetoed);
}
} finally {
if (inStream != null) {
inStream.close();
}
}
}
} catch (IOException e) {
throw new RuntimeException("Could not load exclusions from " + EXCLUSIONS, e);
}
return vetoed;
}
//-------------------------------------------------------------------------------------||
// Internal Helper Methods - Service Loading ------------------------------------------||
//-------------------------------------------------------------------------------------||
private void addVetoedClasses(String serviceName, String serviceImpls, ClassLoader classLoader,
Map<Class<?>, Set<Class<?>>> vetoed) {
try {
final Class<?> serviceClass = classLoader.loadClass(serviceName);
final Set<Class<?>> classes = loadVetoedServiceImpl(serviceImpls, classLoader);
final Set<Class<?>> registeredVetoedClasses = vetoed.get(serviceClass);
if (registeredVetoedClasses == null) {
vetoed.put(serviceClass, classes);
} else {
registeredVetoedClasses.addAll(classes);
}
} catch (ClassNotFoundException e) {
// ignores since this is a veto that it is not applicable
}
}
private Set<Class<?>> loadVetoedServiceImpl(String serviceImpls, ClassLoader classLoader) {
final StringTokenizer serviceImplsSeparator = new StringTokenizer(serviceImpls, ",");
final Set<Class<?>> serviceImplsClass = new LinkedHashSet<Class<?>>();
while (serviceImplsSeparator.hasMoreTokens()) {
try {
serviceImplsClass.add(classLoader.loadClass(serviceImplsSeparator.nextToken().trim()));
} catch (ClassNotFoundException e) {
// ignores since this is a veto that it is not applicable
}
}
return serviceImplsClass;
}
private <T> Set<Class<? extends T>> load(Class<T> serviceClass, ClassLoader loader) {
String serviceFile = SERVICES + "/" + serviceClass.getName();
LinkedHashSet<Class<? extends T>> providers = new LinkedHashSet<Class<? extends T>>();
LinkedHashSet<Class<? extends T>> vetoedProviders = new LinkedHashSet<Class<? extends T>>();
try {
Enumeration<URL> enumeration = loader.getResources(serviceFile);
while (enumeration.hasMoreElements()) {
final URL url = enumeration.nextElement();
final InputStream is = url.openStream();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String line = reader.readLine();
while (null != line) {
line = skipCommentAndTrim(line);
if (line.length() > 0) {
try {
boolean mustBeVetoed = line.startsWith("!");
if (mustBeVetoed) {
line = line.substring(1);
}
Class<? extends T> provider = loader.loadClass(line).asSubclass(serviceClass);
if (mustBeVetoed) {
vetoedProviders.add(provider);
}
if (vetoedProviders.contains(provider)) {
providers.remove(provider);
} else {
providers.add(provider);
}
} catch (ClassCastException e) {
throw new IllegalStateException("Service " + line + " does not implement expected type "
+ serviceClass.getName());
}
}
line = reader.readLine();
}
} finally {
if (reader != null) {
reader.close();
}
}
}
} 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 <T> Set<T> createInstances(Class<T> serviceType, Set<Class<? extends T>> providers) {
Set<T> providerImpls = new LinkedHashSet<T>();
for (Class<? extends T> serviceClass : providers) {
providerImpls.add(createInstance(serviceClass));
}
return providerImpls;
}
/**
* Create a new instance of the found Service. <br/>
* <p>
* Verifies that the found ServiceImpl implements Service.
*
* @param serviceType
* The Service interface
* @param className
* The name of the implementation class
* @param loader
* The ClassLoader to load the ServiceImpl from
*
* @return A new instance of the ServiceImpl
*
* @throws Exception
* If problems creating a new instance
*/
private <T> T createInstance(Class<? extends T> serviceImplClass) {
try {
return SecurityActions.newInstance(serviceImplClass, new Class<?>[0], new Object[0]);
} catch (Exception e) {
throw new RuntimeException(
"Could not create a new instance of Service implementation " + serviceImplClass.getName(), e);
}
}
}