/*
* 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.impl.base;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchiveFormat;
import org.jboss.shrinkwrap.api.Assignable;
import org.jboss.shrinkwrap.api.ClassLoaderSearchUtilDelegator;
import org.jboss.shrinkwrap.api.ConfigurationBuilder;
import org.jboss.shrinkwrap.api.ExtensionLoader;
import org.jboss.shrinkwrap.api.UnknownExtensionTypeException;
import org.jboss.shrinkwrap.api.UnknownExtensionTypeExceptionDelegator;
ServiceExtensionLoader This class is the default strategy to load extensions when an instance of ExtensionLoader
is not provided to the ConfigurationBuilder
and the ConfigurationBuilder.build()
method is invoked. If the ConfigurationBuilder
doesn't provide any ClassLoader
, ConfigurationBuilder.build()
defaults to a one-element collection holding the TCCL. The classLoaders
are used to find the provider-configuration file for the extension to be loaded in META-INF/services/. This provider-configuration file is used to make an instance of the SPI implementation and cached in cache
. Author: Aslak Knutsen, Ken Gullaksen Version: $Revision: $
/**
* ServiceExtensionLoader
*
* This class is the default strategy to load extensions when an instance of {@link ExtensionLoader} is not provided to
* the {@link ConfigurationBuilder} and the {@link ConfigurationBuilder#build()} method is invoked. If the
* {@link ConfigurationBuilder} doesn't provide any {@link ClassLoader}, {@link ConfigurationBuilder#build()} defaults
* to a one-element collection holding the TCCL. The {@link ServiceExtensionLoader#classLoaders} are used to find the
* provider-configuration file for the extension to be loaded in META-INF/services/. This provider-configuration file is
* used to make an instance of the SPI implementation and cached in {@link ServiceExtensionLoader#cache}.
*
* @author <a href="mailto:aslak@conduct.no">Aslak Knutsen</a>
* @author <a href="mailto:ken@glxn.net">Ken Gullaksen</a>
* @version $Revision: $
*/
public class ServiceExtensionLoader implements ExtensionLoader {
// -------------------------------------------------------------------------------------||
// Class Members ----------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
Logger
/**
* Logger
*/
@SuppressWarnings("unused")
private static final Logger log = Logger.getLogger(ServiceExtensionLoader.class.getName());
// -------------------------------------------------------------------------------------||
// Instance Members -------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
private Map<Class<?>, Class<?>> cache = new HashMap<Class<?>, Class<?>>();
private Map<Class<?>, ExtensionWrapper> extensionMappings = new HashMap<Class<?>, ExtensionWrapper>();
ClassLoader used for loading extensions
/**
* ClassLoader used for loading extensions
*/
private final Iterable<ClassLoader> classLoaders;
// -------------------------------------------------------------------------------------||
// Constructor ------------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
Creates a new instance, using the specified ClassLoader
s to create extensions Params: - classLoaders –
Throws: - IllegalArgumentException – If the
ClassLoader
is not specified
/**
* Creates a new instance, using the specified {@link ClassLoader}s to create extensions
*
* @param classLoaders
* @throws IllegalArgumentException
* If the {@link ClassLoader} is not specified
*/
public ServiceExtensionLoader(final Iterable<ClassLoader> classLoaders) throws IllegalArgumentException {
if (classLoaders == null) {
throw new IllegalArgumentException("ClassLoader must be specified");
}
this.classLoaders = classLoaders;
}
// -------------------------------------------------------------------------------------||
// Required Implementations - ExtensionLoader -----------------------------------------||
// -------------------------------------------------------------------------------------||
{@inheritDoc}
See Also:
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.ExtensionLoader#load(java.lang.Class, org.jboss.shrinkwrap.api.Archive)
*/
@Override
public <T extends Assignable> T load(Class<T> extensionClass, Archive<?> baseArchive)
throws UnknownExtensionTypeException {
if (isCached(extensionClass)) {
return createFromCache(extensionClass, baseArchive);
}
T object = createFromLoadExtension(extensionClass, baseArchive);
addToCache(extensionClass, object.getClass());
return object;
}
// -------------------------------------------------------------------------------------||
// Internal Helper Methods - Cache ----------------------------------------------------||
// -------------------------------------------------------------------------------------||
boolean isCached(Class<?> extensionClass) {
return cache.containsKey(extensionClass);
}
private <T extends Assignable> T createFromCache(Class<T> extensionClass, Archive<?> archive) {
Class<T> extensionImplClass = getFromCache(extensionClass);
return createExtension(extensionImplClass, archive);
}
void addToCache(Class<?> extensionClass, Class<?> extensionImplClass) {
cache.put(extensionClass, extensionImplClass);
}
@SuppressWarnings("unchecked")
<T extends Assignable> Class<T> getFromCache(Class<T> extensionClass) {
return (Class<T>) cache.get(extensionClass);
}
// -------------------------------------------------------------------------------------||
// Internal Helper Methods - Override -------------------------------------------------||
// -------------------------------------------------------------------------------------||
{@inheritDoc}
See Also:
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.ExtensionLoader#addOverride(java.lang.Class, java.lang.Class)
*/
public <T extends Assignable> ServiceExtensionLoader addOverride(final Class<T> extensionClass,
final Class<? extends T> extensionImplClass) {
addToCache(extensionClass, extensionImplClass);
return this;
}
{@inheritDoc}
See Also:
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.ExtensionLoader#getExtensionFromExtensionMapping(java.lang.Class)
*/
public <T extends Assignable> String getExtensionFromExtensionMapping(final Class<T> type) {
ExtensionWrapper extensionWrapper = extensionMappings.get(type);
if (extensionWrapper == null) {
loadExtensionMapping(type);
}
extensionWrapper = extensionMappings.get(type);
if (extensionWrapper == null) {
throw UnknownExtensionTypeExceptionDelegator.newExceptionInstance(type);
}
return extensionWrapper.getProperty("extension");
}
{@inheritDoc}
See Also:
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.ExtensionLoader#getArchiveFormatFromExtensionMapping(java.lang.Class)
*/
public <T extends Archive<T>> ArchiveFormat getArchiveFormatFromExtensionMapping(final Class<T> type) {
ExtensionWrapper extensionWrapper = extensionMappings.get(type);
if (extensionWrapper == null) {
loadExtensionMapping(type);
}
extensionWrapper = extensionMappings.get(type);
if (extensionWrapper == null) {
throw UnknownExtensionTypeExceptionDelegator.newExceptionInstance(type);
}
String archiveFormat = extensionWrapper.getProperty("archiveFormat");
return ArchiveFormat.valueOf(archiveFormat);
}
Check to see if a specific extension interface is beeing overloaded
Params: - extensionClass –
The ExtensionType interface class
Returns: true if found
/**
* Check to see if a specific extension interface is beeing overloaded
*
* @param extensionClass
* The ExtensionType interface class
* @return true if found
*/
public boolean isOverriden(Class<?> extensionClass) {
return isCached(extensionClass);
}
// -------------------------------------------------------------------------------------||
// Internal Helper Methods - Loading --------------------------------------------------||
// -------------------------------------------------------------------------------------||
Creates a new instance of a extensionClass
implementation. The implementation class is found in a
provider-configuration file in META-INF/services/
Params: - extensionClass –
- archive –
Type parameters: - <T> –
Returns: an instance of the extensionClass
' implementation.
/**
* Creates a new instance of a <code>extensionClass</code> implementation. The implementation class is found in a
* provider-configuration file in META-INF/services/
*
* @param <T>
* @param extensionClass
* @param archive
* @return an instance of the <code>extensionClass</code>' implementation.
*/
private <T extends Assignable> T createFromLoadExtension(Class<T> extensionClass, Archive<?> archive) {
ExtensionWrapper extensionWrapper = loadExtensionMapping(extensionClass);
if (extensionWrapper == null) {
throw new RuntimeException("Failed to load ExtensionMapping");
}
Class<T> extensionImplClass = loadExtension(extensionWrapper);
if (!extensionClass.isAssignableFrom(extensionImplClass)) {
throw new RuntimeException("Found extension impl class " + extensionImplClass.getName()
+ " not assignable to extension interface " + extensionClass.getName());
}
return createExtension(extensionImplClass, archive);
}
Loads the implementation class hold in ExtensionWrapper.implementingClassName
Params: - extensionWrapper –
Type parameters: - <T> –
Returns:
/**
* Loads the implementation class hold in {@link ExtensionWrapper#implementingClassName}
*
* @param <T>
* @param extensionWrapper
* @return
*/
private <T extends Assignable> Class<T> loadExtension(ExtensionWrapper extensionWrapper) {
return loadExtensionClass(extensionWrapper.implementingClassName);
}
Finds the SPI configuration, wraps it into a ExtensionWrapper
and loads it to extensionMappings
. Params: - extensionClass –
Type parameters: - <T> –
Returns:
/**
* Finds the SPI configuration, wraps it into a {@link ExtensionWrapper} and loads it to
* {@link ServiceExtensionLoader#extensionMappings}.
*
* @param <T>
* @param extensionClass
* @return
*/
private <T extends Assignable> ExtensionWrapper loadExtensionMapping(Class<T> extensionClass) {
final InputStream extensionStream = findExtensionImpl(extensionClass);
ExtensionWrapper extensionWrapper = loadExtensionWrapper(extensionStream, extensionClass);
this.extensionMappings.put(extensionClass, extensionWrapper);
return extensionWrapper;
}
Iterates through the classloaders to load the provider-configuration file for extensionClass
in
META-INF/services/ using its binary name.
Params: - extensionClass –
SPI type for which the configuration file is looked for
Type parameters: - <T> –
Throws: - RuntimeException –
if it doesn't find a provider-configuration file for
extensionClass
- UnknownExtensionTypeExceptionDelegator –
Returns: An InputStream
representing extensionClass
's configuration file
/**
* Iterates through the classloaders to load the provider-configuration file for <code>extensionClass</code> in
* META-INF/services/ using its binary name.
*
* @param <T>
* @param extensionClass
* SPI type for which the configuration file is looked for
* @return An {@link InputStream} representing <code>extensionClass</code>'s configuration file
* @throws RuntimeException
* if it doesn't find a provider-configuration file for <code>extensionClass</code>
* @throws UnknownExtensionTypeExceptionDelegator
*/
private <T extends Assignable> InputStream findExtensionImpl(final Class<T> extensionClass) {
try {
// Add all extension impls found in all CLs
for (final ClassLoader cl : this.getClassLoaders()) {
final InputStream stream = cl.getResourceAsStream("META-INF/services/" + extensionClass.getName());
if (stream != null) {
return stream;
}
}
// None found
throw new RuntimeException("No extension implementation found for " + extensionClass.getName()
+ ", please verify classpath or add a extensionOverride");
} catch (Exception e) {
throw UnknownExtensionTypeExceptionDelegator.newExceptionInstance(extensionClass);
}
}
Wraps the provider-configuration file extensionStream
, the SPI extensionClass
and its implementation class name into a ExtensionWrapper
instance. Params: - extensionStream –
- a bytes stream representation of the provider-configuration file
- extensionClass –
- SPI type
Type parameters: - <T> –
Returns: a ExtensionWrapper
instance
/**
* Wraps the provider-configuration file <code>extensionStream</code>, the SPI <code>extensionClass</code> and its
* implementation class name into a {@link ExtensionWrapper} instance.
*
* @param <T>
* @param extensionStream
* - a bytes stream representation of the provider-configuration file
* @param extensionClass
* - SPI type
* @return a {@link ExtensionWrapper} instance
*/
private <T extends Assignable> ExtensionWrapper loadExtensionWrapper(final InputStream extensionStream,
Class<T> extensionClass) {
Properties properties = new Properties();
try {
properties.load(extensionStream);
} catch (IOException e) {
throw new RuntimeException("Could not open stream for extensionURL " + extensionStream, e);
}
String implementingClassName = (String) properties.get("implementingClassName");
if (implementingClassName == null) {
throw new RuntimeException("Property implementingClassName is not present in " + extensionStream);
}
final Map<String, String> map = new HashMap<String, String>(properties.size());
final Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()) {
final String key = (String) keys.nextElement();
final String value = (String) properties.get(key);
map.put(key, value);
}
return new ExtensionWrapper(implementingClassName, map, extensionClass);
}
Delegates class loading of extensionClassName
to ClassLoaderSearchUtilDelegator.findClassFromClassLoaders(String, Iterable<ClassLoader>)
passing the extensionClassName
and the instance's classLoaders
.
Params: - extensionClassName –
Type parameters: - <T> –
Returns:
/**
* Delegates class loading of <code>extensionClassName</code> to
* {@link ClassLoaderSearchUtilDelegator#findClassFromClassLoaders(String, Iterable)} passing the
* <code>extensionClassName</code> and the instance's <code>classLoaders</code>.
*
* @param <T>
* @param extensionClassName
* @return
*/
@SuppressWarnings("unchecked")
private <T extends Assignable> Class<T> loadExtensionClass(String extensionClassName) {
try {
return (Class<T>) ClassLoaderSearchUtilDelegator.findClassFromClassLoaders(extensionClassName,
getClassLoaders());
} catch (final ClassNotFoundException e) {
throw new RuntimeException("Could not load class " + extensionClassName, e);
}
}
Creates an instance of extensionImplClass
using archive
as the parameter for its
one-argument list constructor.
Params: - extensionImplClass –
- archive –
Type parameters: - <T> –
Returns:
/**
* Creates an instance of <code>extensionImplClass</code> using <code>archive</code> as the parameter for its
* one-argument list constructor.
*
* @param <T>
* @param extensionImplClass
* @param archive
* @return
*/
private <T extends Assignable> T createExtension(Class<T> extensionImplClass, Archive<?> archive) {
T extension;
Constructor<T> extensionImplConstructor = findConstructor(extensionImplClass);
@SuppressWarnings("unchecked")
Class<T> constructorArg = (Class<T>) extensionImplConstructor.getParameterTypes()[0];
try {
if (constructorArg.isInstance(archive)) {
extension = extensionImplConstructor.newInstance(archive);
} else {
extension = extensionImplConstructor.newInstance(load(constructorArg, archive));
}
} catch (InstantiationException e) {
throw new ExtensionLoadingException("Failed to instantiate class of type " + archive.getClass()
+ ". The underlying class can not be abstract.", e);
} catch (IllegalAccessException e) {
throw new ExtensionLoadingException("Failed to instantiate class of type " + archive.getClass()
+ ". The underlying constructor is inaccessible.", e);
} catch (InvocationTargetException e) {
throw new ExtensionLoadingException("Failed to instantiate class of type " + archive.getClass()
+ ". The underlying constructor threw an exception.", e);
}
return extension;
}
Finds a constructor with a one-argument list's element which implements Archive
. Params: - extensionImplClass – - Implementation of
Assignable
with a one-argument list's element which implements Archive
.
Type parameters: - <T> –
Returns:
/**
* Finds a constructor with a one-argument list's element which implements {@link Archive}.
*
* @param <T>
* @param extensionImplClass
* - Implementation of {@link Assignable} with a one-argument list's element which implements
* {@link Archive}.
* @return
*/
@SuppressWarnings("unchecked")
private <T extends Assignable> Constructor<T> findConstructor(Class<T> extensionImplClass) {
Constructor<?>[] constructors = SecurityActions.getConstructors(extensionImplClass);
for (Constructor<?> constructor : constructors) {
Class<?>[] parameters = constructor.getParameterTypes();
if (parameters.length != 1) {
continue;
}
Class<?> parameter = parameters[0];
if (Archive.class.isAssignableFrom(parameter)) {
return (Constructor<T>) constructor;
}
}
throw new ExtensionLoadingException("No constructor with a single argument of type " + Archive.class.getName()
+ " could be found");
}
private Iterable<ClassLoader> getClassLoaders() {
return this.classLoaders;
}
}