/** * This file Copyright (c) 2011-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.test.mock; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.magnolia.cms.util.DeprecationUtil; import info.magnolia.objectfactory.Classes; import info.magnolia.objectfactory.ComponentFactory; import info.magnolia.objectfactory.ComponentFactoryUtil; import info.magnolia.objectfactory.ComponentProvider; import info.magnolia.objectfactory.ConfiguredComponentFactory; import info.magnolia.objectfactory.LazyObservedComponentFactory; import info.magnolia.objectfactory.MgnlInstantiationException; import info.magnolia.objectfactory.configuration.ComponentConfiguration; import info.magnolia.objectfactory.configuration.ProviderConfiguration; import info.magnolia.objectfactory.configuration.ComponentProviderConfiguration; import info.magnolia.objectfactory.configuration.ConfiguredComponentConfiguration; import info.magnolia.objectfactory.configuration.ImplementationConfiguration; import info.magnolia.objectfactory.configuration.InstanceConfiguration; /** * Abstract ComponentProvider that supports component factories. Subclasses are responsible for registering components * and factories. Both components and factories are created on-demand when registered as classes. * * @version $Id$ */ public abstract class AbstractComponentProvider implements ComponentProvider { private static class ComponentDefinition<T> { private Class<T> type; private T instance; private ComponentFactory<T> factory; private Class<? extends ComponentFactory<T>> factoryClass; private Class<? extends T> implementationType; public Class<T> getType() { return type; } public void setType(Class<T> type) { this.type = type; } public T getInstance() { return instance; } public void setInstance(T instance) { this.instance = instance; } public ComponentFactory<T> getFactory() { return factory; } public void setFactory(ComponentFactory<T> factory) { this.factory = factory; } /** * The implementation type is unknown for components created by ComponentFactoryUtil. */ public Class<? extends T> getImplementationType() { return implementationType; } public void setImplementationType(Class<? extends T> implementationType) { this.implementationType = implementationType; } public Class<? extends ComponentFactory<T>> getFactoryClass() { return factoryClass; } public void setFactoryClass(Class<? extends ComponentFactory<T>> factoryClass) { this.factoryClass = factoryClass; } public boolean isFactory() { return factoryClass != null || factory != null; } } private final Logger log = LoggerFactory.getLogger(getClass()); private ComponentProvider parent; private final Map<Class<?>, ComponentDefinition<?>> definitions = new ConcurrentHashMap<Class<?>, ComponentDefinition<?>>(); private final ThreadLocal<List<Class<?>>> creationStack = new ThreadLocal<List<Class<?>>>(); protected AbstractComponentProvider() { } protected AbstractComponentProvider(ComponentProvider parent) { this(); this.parent = parent; } @Override public ComponentProvider getParent() { return parent; } @Override public <T> T getSingleton(Class<T> type) { DeprecationUtil.isDeprecated(); return getComponent(type); } @Override public synchronized <T> T getComponent(Class<T> type) { ComponentDefinition<T> definition = getComponentDefinition(type); if (definition == null) { if (parent != null) { return parent.getComponent(type); } // Register the component on-demand if (!Classes.isConcrete(type)) { throw new MgnlInstantiationException("No concrete implementation defined for " + type); } registerImplementation(type, type); definition = getComponentDefinition(type); } T instance = definition.getInstance(); if (instance == null) { log.debug("No instance for {} yet, creating new one.", type); instance = newInstance(type); definition.setInstance(instance); log.debug("New instance for {} created: {}", type, instance); } return instance; } @Override public synchronized <T> T newInstance(Class<T> type, Object... parameters) { if (type == null) { log.error("type can't be null", new Throwable()); return null; } List<Class<?>> stack = creationStack.get(); try { if (stack == null) { stack = new ArrayList<Class<?>>(); stack.add(type); creationStack.set(stack); } else if (stack.contains(type)) { throw new CircularDependencyException(type, stack); } else { stack.add(type); } ComponentDefinition<T> definition = getComponentDefinition(type); if (definition == null) { if (parent != null) { return parent.newInstance(type, parameters); } if (!Classes.isConcrete(type)) { throw new MgnlInstantiationException("No concrete implementation defined for " + type); } return createInstance(type, parameters); } if (definition.isFactory()) { return this.<T>instantiateFactoryIfNecessary(definition).newInstance(); } return createInstance(definition.getImplementationType(), parameters); } catch (Exception e) { if (e instanceof MgnlInstantiationException) { throw (MgnlInstantiationException) e; } throw new MgnlInstantiationException("Can't instantiate an implementation of this class [" + type.getName() + "]: " + ExceptionUtils.getMessage(e), e); } finally { stack.remove(type); if (stack.isEmpty()) { creationStack.remove(); } } } @Override public <T> Class<? extends T> getImplementation(Class<T> type) throws ClassNotFoundException { ComponentDefinition<T> definition = getComponentDefinition(type); if (definition == null) { if (parent != null) { return parent.getImplementation(type); } return type; } if (definition.isFactory()) { return type; } return definition.getImplementationType(); } @SuppressWarnings({"rawtypes", "unchecked"}) public void configure(ComponentProviderConfiguration configuration) { for (Map.Entry<Class, ComponentConfiguration> entry : configuration.getComponents().entrySet()) { ComponentConfiguration value = entry.getValue(); if (value instanceof ImplementationConfiguration) { ImplementationConfiguration config = (ImplementationConfiguration) value; registerImplementation(config.getType(), config.getImplementation()); } else if (value instanceof InstanceConfiguration) { InstanceConfiguration config = (InstanceConfiguration) value; registerInstance(config.getType(), config.getInstance()); } else if (value instanceof ProviderConfiguration) { ProviderConfiguration config = (ProviderConfiguration) value; registerComponentFactory(config.getType(), config.getProviderClass()); } else if (value instanceof ConfiguredComponentConfiguration) { ConfiguredComponentConfiguration config = (ConfiguredComponentConfiguration) value; registerConfiguredComponent(config.getType(), config.getWorkspace(), config.getPath(), config.isObserved()); } } for (Map.Entry<Class<?>, Class<?>> entry : configuration.getTypeMapping().entrySet()) { registerImplementation((Class)entry.getKey(), (Class)entry.getValue()); } } private <T> void registerComponentFactory(Class<T> type, Class<? extends ComponentFactory<T>> factoryClass) { ComponentDefinition<T> definition = new ComponentDefinition<T>(); definition.setType(type); definition.setFactoryClass(factoryClass); definitions.put(type, definition); } public synchronized <T> void registerConfiguredComponent(Class<T> type, final String workspace, final String path, boolean observed) { final ComponentFactory<T> factory; if (observed) { factory = new LazyObservedComponentFactory<T>(workspace, path, type, this); } else { factory = new ConfiguredComponentFactory<T>(path, workspace, this); } registerComponentFactory(type, factory); } @SuppressWarnings("unchecked") public synchronized <T> void registerImplementation(Class<T> type, Class<? extends T> implementationType) { if (definitions.containsKey(type)) throw new MgnlInstantiationException("Component already registered for type " + type.getName()); if (implementationType == null) { throw new MgnlInstantiationException("ImplementationConfiguration type is not set a for type " + type); } if (!Classes.isConcrete(implementationType)) { throw new MgnlInstantiationException("ImplementationConfiguration type is not a concrete class for type " + type); } if (ComponentFactory.class.isAssignableFrom(implementationType)) { ComponentDefinition<T> definition = new ComponentDefinition<T>(); definition.setType(type); definition.setFactoryClass((Class<? extends ComponentFactory<T>>)implementationType); definitions.put(type, definition); } else { ComponentDefinition<T> definition = new ComponentDefinition<T>(); definition.setType(type); definition.setImplementationType(implementationType); definitions.put(type, definition); } } public synchronized <T> void registerComponentFactory(Class<T> type, ComponentFactory<T> componentFactory) { if (type == null) { throw new NullPointerException("Null type is not allowed. Please check your type definition and classes on the classpath."); } if (definitions.containsKey(type)) { throw new MgnlInstantiationException("Component already registered for type " + type.getName()); } ComponentDefinition<T> definition = new ComponentDefinition<T>(); definition.setType(type); definition.setFactory(componentFactory); definitions.put(type, definition); } public synchronized <T> void registerInstance(Class<T> type, T instance) { if (definitions.containsKey(type)) { throw new MgnlInstantiationException("Component already registered for type " + type.getName()); } ComponentDefinition<T> definition = new ComponentDefinition<T>(); definition.setType(type); definition.setInstance(instance); definitions.put(type, definition); } @SuppressWarnings("unchecked") private <T> ComponentDefinition<T> getComponentDefinition(Class<T> type) { return (ComponentDefinition<T>)definitions.get(type); } private <T> ComponentFactory<T> instantiateFactoryIfNecessary(ComponentDefinition<T> definition) throws InvocationTargetException, IllegalAccessException, InstantiationException { if (definition.getFactoryClass() != null && definition.getFactory() == null) { definition.setFactory(ComponentFactoryUtil.createFactory(definition.getFactoryClass(), this)); } return definition.getFactory(); } protected <T> T createInstance(Class<T> implementationType, Object... parameters) { return Classes.getClassFactory().newInstance(implementationType, parameters); } protected synchronized void removeComponent(Class<?> type) { definitions.remove(type); } protected synchronized void clear() { definitions.clear(); } }