org.jboss.shrinkwrap.impl.base.ServiceExtensionLoader Maven / Gradle / Ivy
Show all versions of shrinkwrap-impl-base 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.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 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 {@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 Aslak Knutsen
* @author Ken Gullaksen
* @version $Revision: $
*/
public class ServiceExtensionLoader implements ExtensionLoader {
// -------------------------------------------------------------------------------------||
// Instance Members -------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
private final Map, Class>> cache = new HashMap<>();
private final Map, ExtensionWrapper> extensionMappings = new HashMap<>();
/**
* ClassLoader used for loading extensions
*/
private final Iterable classLoaders;
// -------------------------------------------------------------------------------------||
// Constructor ------------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Creates a new instance, using the specified {@link ClassLoader}s to create extensions
*
* @param classLoaders
* The class loaders to be used to load extension implementations
* @throws IllegalArgumentException
* If the {@link ClassLoader} is not specified
*/
public ServiceExtensionLoader(final Iterable classLoaders) throws IllegalArgumentException {
if (classLoaders == null) {
throw new IllegalArgumentException("ClassLoader must be specified");
}
this.classLoaders = classLoaders;
}
// -------------------------------------------------------------------------------------||
// Required Implementations - ExtensionLoader -----------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.ExtensionLoader#load(java.lang.Class, org.jboss.shrinkwrap.api.Archive)
*/
@Override
public T load(Class 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 createFromCache(Class extensionClass, Archive> archive) {
Class extensionImplClass = getFromCache(extensionClass);
return createExtension(extensionImplClass, archive);
}
void addToCache(Class> extensionClass, Class> extensionImplClass) {
cache.put(extensionClass, extensionImplClass);
}
@SuppressWarnings("unchecked")
Class getFromCache(Class extensionClass) {
return (Class) cache.get(extensionClass);
}
// -------------------------------------------------------------------------------------||
// Internal Helper Methods - Override -------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.ExtensionLoader#addOverride(java.lang.Class, java.lang.Class)
*/
public ServiceExtensionLoader addOverride(final Class extensionClass,
final Class extends T> extensionImplClass) {
addToCache(extensionClass, extensionImplClass);
return this;
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.api.ExtensionLoader#getExtensionFromExtensionMapping(java.lang.Class)
*/
public String getExtensionFromExtensionMapping(final Class 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 org.jboss.shrinkwrap.api.ExtensionLoader#getArchiveFormatFromExtensionMapping(java.lang.Class)
*/
public > ArchiveFormat getArchiveFormatFromExtensionMapping(final Class 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 being overloaded
*
* @param extensionClass
* The ExtensionType interface class
* @return true if found
*/
public boolean isOverridden(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/
*
* @param
* The type of the extension class that extends {@link Assignable}.
* @param extensionClass
* The class of the extension to be created.
* @param archive
* The archive used to create the extension instance.
* @return an instance of the extensionClass
implementation.
*/
private T createFromLoadExtension(Class extensionClass, Archive> archive) {
ExtensionWrapper extensionWrapper = loadExtensionMapping(extensionClass);
if (extensionWrapper == null) {
throw new RuntimeException("Failed to load ExtensionMapping");
}
Class 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 {@link ExtensionWrapper#implementingClassName}
*
* @param
* The type of the extension class that extends {@link Assignable}.
* @param extensionWrapper
* The wrapper containing the name of the implementing class.
* @return The {@link Class} object for the implementation class specified in the {@code extensionWrapper}.
*/
private Class loadExtension(ExtensionWrapper extensionWrapper) {
return loadExtensionClass(extensionWrapper.implementingClassName);
}
/**
* Finds the SPI configuration, wraps it into a {@link ExtensionWrapper} and loads it to
* {@link ServiceExtensionLoader#extensionMappings}.
*
* @param
* The type of the extension class that extends {@link Assignable}.
* @param extensionClass
* The class of the extension to be mapped.
* @return An {@link ExtensionWrapper} containing the SPI configuration for the specified {@code extensionClass}.
*/
private ExtensionWrapper loadExtensionMapping(Class 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.
*
* @param
* The type of the extension class that extends {@link Assignable}.
* @param extensionClass
* SPI type for which the configuration file is looked for
* @return An {@link InputStream} representing extensionClass
's configuration file
* @throws RuntimeException
* if it doesn't find a provider-configuration file for extensionClass
* @throws UnknownExtensionTypeExceptionDelegator
*/
private InputStream findExtensionImpl(final Class 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 {@link ExtensionWrapper} instance.
*
* @param
* The type of the extension class that extends {@link Assignable}.
* @param extensionStream
* - a bytes stream representation of the provider-configuration file
* @param extensionClass
* - SPI type
* @return a {@link ExtensionWrapper} instance
*/
private ExtensionWrapper loadExtensionWrapper(final InputStream extensionStream,
Class 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 map = new HashMap<>(properties.size());
final Enumeration