/*
 * 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 ClassLoaders to create extensions
Params:
  • classLoaders –
Throws:
/** * 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:
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; } }