/*
* Copyright (C) 2013 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.container.jmx;
import org.exoplatform.commons.utils.ClassLoading;
import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.ComponentAdapterDependenciesAware;
import org.exoplatform.container.ComponentTask;
import org.exoplatform.container.ComponentTaskContext;
import org.exoplatform.container.ComponentTaskType;
import org.exoplatform.container.ConcurrentContainer.CreationalContextComponentAdapter;
import org.exoplatform.container.ConcurrentContainerMT;
import org.exoplatform.container.CyclicDependencyException;
import org.exoplatform.container.Dependency;
import org.exoplatform.container.DependencyStackListener;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.LockManager;
import org.exoplatform.container.component.ComponentLifecycle;
import org.exoplatform.container.component.ComponentPlugin;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.util.ContainerUtil;
import org.exoplatform.container.xml.Component;
import org.exoplatform.container.xml.ExternalComponentPlugins;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.picocontainer.Startable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicReference;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.CreationalContext;
import javax.inject.Singleton;
/**
* @author <a href="mailto:nfilotto@exoplatform.com">Nicolas Filotto</a>
* @version $Id$
*
*/
public class MX4JComponentAdapterMT<T> extends MX4JComponentAdapter<T> implements DependencyStackListener,
ComponentAdapterDependenciesAware<T>
{
/**
* Serial Version ID
*/
private static final long serialVersionUID = -9001193588034229411L;
private transient final AtomicReference<Collection<Dependency>> createDependencies =
new AtomicReference<Collection<Dependency>>();
private transient final AtomicReference<Collection<Dependency>> initDependencies =
new AtomicReference<Collection<Dependency>>();
/**
* The task to use to create the component
*/
private transient final AtomicReference<ComponentTask<T>> createTask = new AtomicReference<ComponentTask<T>>();
/**
* The task to use to init the component
*/
private transient final AtomicReference<Collection<ComponentTask<Void>>> initTasks =
new AtomicReference<Collection<ComponentTask<Void>>>();
/** . */
private transient final ConcurrentContainerMT container;
private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.mt.MX4JComponentAdapterMT");
public MX4JComponentAdapterMT(ExoContainer holder, ConcurrentContainerMT container, Object key,
Class<T> implementation)
{
super(holder, container, key, implementation, LockManager.getInstance().createLock());
this.container = container;
}
private void addComponentPlugin(List<ComponentTask<Void>> tasks, Set<Dependency> dependencies, boolean debug,
List<org.exoplatform.container.xml.ComponentPlugin> plugins) throws Exception
{
if (plugins == null)
return;
for (org.exoplatform.container.xml.ComponentPlugin plugin : plugins)
{
try
{
Class<?> pluginClass = ClassLoading.forName(plugin.getType(), this);
List<Dependency> lDependencies = new ArrayList<Dependency>();
@SuppressWarnings("unchecked")
Constructor<T> constructor = (Constructor<T>)container.getConstructor(pluginClass, lDependencies);
dependencies.addAll(lDependencies);
tasks.add(createPlugin(this, container, pluginClass, debug, plugin, constructor, plugin.getInitParams(),
lDependencies));
}
catch (CyclicDependencyException e)
{
throw e;
}
catch (Exception ex)
{
LOG.error("Failed to instanciate plugin " + plugin.getName() + " for component "
+ getComponentImplementation() + ": " + ex.getMessage(), ex);
}
}
}
/**
* {@inheritDoc}
*/
public Collection<Dependency> getCreateDependencies()
{
return createDependencies.get();
}
/**
* {@inheritDoc}
*/
public Collection<Dependency> getInitDependencies()
{
return initDependencies.get();
}
/**
* {@inheritDoc}
*/
public void callDependency(ComponentTask<?> task, Dependency dep)
{
if (PropertyManager.isDevelopping())
{
if (dep.getKey() instanceof String
|| (dep.getKey() instanceof Class && ((Class<?>)dep.getKey()).isAnnotation()))
{
LOG.warn("An unexpected call of getComponentInstance(" + dep.getKey() + "," + dep.getBindType().getName()
+ ") has been detected please add the component in your constructor instead", new Exception(
"This is the stack trace allowing you to identify where the unexpected "
+ "call of getComponentInstanceOfType has been done"));
}
else if (dep.getKey() instanceof Class)
{
LOG.warn("An unexpected call of getComponentInstanceOfType(" + ((Class<?>)dep.getKey()).getName()
+ ") has been detected please add the component in your constructor instead", new Exception(
"This is the stack trace allowing you to identify where the unexpected "
+ "call of getComponentInstanceOfType has been done"));
}
}
if (dep.getKey().equals(getComponentKey()))
{
return;
}
if (task.getType() == ComponentTaskType.CREATE)
{
getCreateDependencies().add(dep);
}
else if (task.getType() == ComponentTaskType.INIT)
{
getInitDependencies().add(dep);
}
container.getComponentTaskContext().checkDependency(dep.getKey(), task.getType());
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
protected ComponentTask<T> getCreateTask()
{
Component component = null;
String componentKey;
InitParams params = null;
boolean debug = false;
// Get the component
Object key = getComponentKey();
if (key instanceof String)
componentKey = (String)key;
else
componentKey = ((Class<?>)key).getName();
try
{
ConfigurationManager manager =
(ConfigurationManager)exocontainer.getComponentInstanceOfType(ConfigurationManager.class);
component = manager == null ? null : manager.getComponent(componentKey);
if (component != null)
{
params = component.getInitParams();
debug = component.getShowDeployInfo();
}
if (debug)
LOG.debug("==> get constructor of the component : " + getComponentImplementation());
List<Dependency> lDependencies = new ArrayList<Dependency>();
Constructor<?> constructor = container.getConstructor(getComponentImplementation(), lDependencies);
setCreateDependencies(lDependencies);
if (debug)
LOG.debug("==> create component : " + getComponentImplementation());
return (ComponentTask<T>)container.createComponentTask(constructor, params, lDependencies, this);
}
catch (Exception e)
{
String msg = "Cannot instantiate component " + getComponentImplementation();
if (component != null)
{
msg =
"Cannot instantiate component key=" + component.getKey() + " type=" + component.getType() + " found at "
+ component.getDocumentURL();
}
throw new RuntimeException(msg, e);
}
}
protected void setCreateDependencies(List<Dependency> lDependencies)
{
if (createDependencies.get() == null)
{
createDependencies.compareAndSet(null, new CopyOnWriteArraySet<Dependency>(lDependencies));
}
}
/**
* {@inheritDoc}
*/
protected Collection<ComponentTask<Void>> getInitTasks()
{
Component component = null;
String componentKey;
boolean debug = false;
// Get the component
Object key = getComponentKey();
if (key instanceof String)
componentKey = (String)key;
else
componentKey = ((Class<?>)key).getName();
try
{
ConfigurationManager manager =
(ConfigurationManager)exocontainer.getComponentInstanceOfType(ConfigurationManager.class);
component = manager == null ? null : manager.getComponent(componentKey);
if (component != null)
{
debug = component.getShowDeployInfo();
}
List<ComponentTask<Void>> tasks = new ArrayList<ComponentTask<Void>>();
Set<Dependency> dependencies = new HashSet<Dependency>();
final Class<T> implementationClass = getComponentImplementation();
boolean isSingleton = this.isSingleton;
boolean isInitialized = this.isInitialized;
if (debug)
LOG.debug("==> create component : " + implementationClass.getName());
boolean hasInjectableConstructor = !isSingleton || ContainerUtil.hasInjectableConstructor(implementationClass);
boolean hasOnlyEmptyPublicConstructor =
!isSingleton || ContainerUtil.hasOnlyEmptyPublicConstructor(implementationClass);
if (hasInjectableConstructor || hasOnlyEmptyPublicConstructor)
{
// There is at least one constructor JSR 330 compliant or we already know
// that it is not a singleton such that the new behavior is expected
List<Dependency> lDependencies = new ArrayList<Dependency>();
boolean isInjectPresent = container.initializeComponent(implementationClass, lDependencies, tasks, this);
dependencies.addAll(lDependencies);
isSingleton = manageScope(isSingleton, isInitialized, hasInjectableConstructor, isInjectPresent);
}
else if (!isInitialized)
{
// The adapter has not been initialized yet
// The old behavior is expected as there is no constructor JSR 330 compliant
isSingleton = this.isSingleton = true;
scope.set(Singleton.class);
}
if (component != null && component.getComponentPlugins() != null)
{
addComponentPlugin(tasks, dependencies, debug, component.getComponentPlugins());
}
ExternalComponentPlugins ecplugins =
manager == null ? null : manager.getConfiguration().getExternalComponentPlugins(componentKey);
if (ecplugins != null)
{
addComponentPlugin(tasks, dependencies, debug, ecplugins.getComponentPlugins());
}
initDependencies.compareAndSet(null, new CopyOnWriteArraySet<Dependency>(dependencies));
tasks.add(new ComponentTask<Void>("initialize component " + getComponentImplementation().getName(), container,
this, ComponentTaskType.INIT)
{
public Void execute(CreationalContextComponentAdapter<?> cCtx) throws Exception
{
// check if component implement the ComponentLifecycle
if (cCtx.get() instanceof ComponentLifecycle && exocontainer instanceof ExoContainer)
{
ComponentLifecycle lc = (ComponentLifecycle)cCtx.get();
lc.initComponent((ExoContainer)exocontainer);
}
return null;
}
});
if (!isInitialized)
{
this.isInitialized = true;
}
return tasks;
}
catch (Exception e)
{
String msg = "Cannot initialize component " + getComponentImplementation();
if (component != null)
{
msg =
"Cannot initialize component key=" + component.getKey() + " type=" + component.getType() + " found at "
+ component.getDocumentURL();
}
throw new RuntimeException(msg, e);
}
}
private ComponentTask<Void> createPlugin(final MX4JComponentAdapterMT<T> caller,
final ConcurrentContainerMT exocontainer, final Class<?> pluginClass, final boolean debug,
final org.exoplatform.container.xml.ComponentPlugin plugin, final Constructor<T> constructor, InitParams params,
List<Dependency> lDependencies) throws Exception
{
final Object[] args = exocontainer.getArguments(constructor, params, lDependencies);
return new ComponentTask<Void>("create/add plugin " + plugin.getName() + " for component "
+ getComponentImplementation().getName(), exocontainer, caller, ComponentTaskType.INIT)
{
public Void execute(final CreationalContextComponentAdapter<?> cCtx) throws Exception
{
try
{
getContainer().loadArguments(args);
ComponentPlugin cplugin = (ComponentPlugin)constructor.newInstance(args);
cplugin.setName(plugin.getName());
cplugin.setDescription(plugin.getDescription());
Class<?> clazz = getComponentImplementation();
final Method m = getSetMethod(clazz, plugin.getSetMethod(), pluginClass);
if (m == null)
{
LOG.error("Cannot find the method '" + plugin.getSetMethod()
+ "' that has only one parameter of type '" + pluginClass.getName() + "' in the class '"
+ clazz.getName() + "'.");
return null;
}
final Object[] params = {cplugin};
SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<Void>()
{
public Void run() throws Exception
{
m.invoke(cCtx.get(), params);
return null;
}
});
if (debug)
LOG.debug("==> add component plugin: " + cplugin);
cplugin.setName(plugin.getName());
cplugin.setDescription(plugin.getDescription());
return null;
}
catch (InvocationTargetException e)
{
if (e.getCause() instanceof Exception)
{
throw (Exception)e.getCause();
}
throw e;
}
}
};
}
protected T createInstance(final Context ctx)
{
T result = ctx.get(this);
if (result != null)
{
return result;
}
return create(new Callable<T>()
{
public T call() throws Exception
{
try
{
return ctx.get(MX4JComponentAdapterMT.this, container.<T> addComponentToCtx(getComponentKey()));
}
finally
{
container.removeComponentFromCtx(getComponentKey());
}
}
});
}
/**
* Must be used to create Singleton or Prototype only
*/
protected T create()
{
return create(new Callable<T>()
{
public T call() throws Exception
{
return doCreate();
}
});
}
/**
* Must be used to create Singleton or Prototype only
*/
protected T doCreate()
{
return doCreate(false);
}
/**
* Must be used to create Singleton or Prototype only
*/
protected T doCreate(boolean useSharedMemory)
{
if (instance_ != null)
{
return instance_;
}
boolean toBeLocked = isSingleton;
boolean skipFinally = false;
try
{
CreationalContextComponentAdapter<T> ctx;
if (toBeLocked)
{
if (useSharedMemory)
{
T result = container.<T> getComponentFromSharedMemory(getComponentKey());
if (result != null)
{
LOG.debug("The value could be found from the shared memory");
skipFinally = true;
return result;
}
LOG.debug("The value could not be found from the shared memory");
}
if (!lock.tryLock())
{
// The lock has already been acquired, let's make sure that we
// don't have any deadlocks
lock.lockInterruptibly();
}
ctx = container.<T> addComponentToCtx(getComponentKey());
}
else
{
// Don't add to context non singleton
skipFinally = true;
ctx = new CreationalContextComponentAdapter<T>();
}
return create(ctx);
}
catch (InterruptedException e)
{
// We make sure that the state of the Thread is back to normal
Thread.interrupted();
skipFinally = true;
LOG.debug("A deadlock has been detected, let's retry using the shared memory");
return doCreate(true);
}
finally
{
if (!skipFinally)
{
lock.unlock();
container.removeComponentFromCtx(getComponentKey());
}
}
}
private T create(Callable<T> mainCreateTask)
{
ComponentTaskContext ctx = container.getComponentTaskContext();
try
{
loadTasks();
loadDependencies(ctx);
return mainCreateTask.call();
}
catch (CyclicDependencyException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException("Cannot create component " + getComponentImplementation(), e);
}
finally
{
if (ctx == null)
{
container.setComponentTaskContext(null);
}
}
}
private void loadDependencies(ComponentTaskContext ctx) throws Exception
{
ComponentTaskContext createCtx = ctx;
if (createCtx == null)
{
createCtx = new ComponentTaskContext(getComponentKey(), ComponentTaskType.CREATE);
container.setComponentTaskContext(createCtx);
}
else if (!createCtx.isLast(getComponentKey()))
{
createCtx = createCtx.addToContext(getComponentKey());
container.setComponentTaskContext(createCtx);
}
container.loadDependencies(getComponentKey(), createCtx, getCreateDependencies(), ComponentTaskType.CREATE);
}
/**
* {@inheritDoc}
*/
public T create(CreationalContext<T> creationalContext)
{
CreationalContextComponentAdapter<T> ctx = (CreationalContextComponentAdapter<T>)creationalContext;
// Avoid to create duplicate instances if it is called at the same time by several threads
if (instance_ != null)
return instance_;
else if (ctx.get() != null)
return ctx.get();
ComponentTaskContext taskCtx = container.getComponentTaskContext();
boolean isRoot = taskCtx.isRoot();
if (!isRoot)
{
container.setComponentTaskContext(taskCtx = taskCtx.setLastTaskType(ComponentTaskType.CREATE));
}
try
{
ComponentTask<T> task = createTask.get();
T result = task.call(ctx);
if (instance_ != null)
{
// Avoid instantiating twice the same component in case of a cyclic reference due
// to component plugins
return instance_;
}
else if (ctx.get() != null)
return ctx.get();
ctx.push(result);
}
catch (CyclicDependencyException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException("Cannot create component " + getComponentImplementation(), e);
}
if (isRoot)
{
container.setComponentTaskContext(taskCtx =
taskCtx.resetDependencies(getComponentKey(), ComponentTaskType.INIT));
}
else
{
container.setComponentTaskContext(taskCtx = taskCtx.setLastTaskType(ComponentTaskType.INIT));
}
Collection<ComponentTask<Void>> tasks = initTasks.get();
ComponentTask<Void> task = null;
try
{
if (tasks != null && !tasks.isEmpty())
{
container.loadDependencies(getComponentKey(), taskCtx, getInitDependencies(), ComponentTaskType.INIT);
for (Iterator<ComponentTask<Void>> it = tasks.iterator(); it.hasNext();)
{
task = it.next();
task.call(ctx);
task = null;
}
}
if (instance_ != null)
{
return instance_;
}
else if (instance_ == null && isSingleton)
{
// In case of cyclic dependency the component could be already initialized
// so we need to recheck the state
instance_ = ctx.get();
}
}
catch (CyclicDependencyException e)
{
throw e;
}
catch (Exception e)
{
if (task != null)
{
throw new RuntimeException("Cannot " + task.getName() + " for the component "
+ getComponentImplementation(), e);
}
throw new RuntimeException("Cannot initialize component " + getComponentImplementation(), e);
}
if (ctx.get() instanceof Startable && exocontainer.canBeStopped())
{
try
{
// Start the component if the container is already started
((Startable)ctx.get()).start();
}
catch (Exception e)
{
throw new RuntimeException("Cannot auto-start component " + getComponentImplementation(), e);
}
}
return ctx.get();
}
private void loadTasks()
{
if (createTask.get() == null)
{
try
{
createTask.compareAndSet(null, getCreateTask());
}
catch (RuntimeException e)
{
throw new RuntimeException("Cannot get the create task of the component " + getComponentImplementation(), e);
}
}
if (initTasks.get() == null)
{
try
{
initTasks.compareAndSet(null, getInitTasks());
}
catch (RuntimeException e)
{
throw new RuntimeException("Cannot get the init tasks of the component " + getComponentImplementation(), e);
}
}
}
}