/*
* Copyright (C) 2009 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 static java.lang.annotation.RetentionPolicy.RUNTIME;
import org.exoplatform.commons.utils.ClassLoading;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.AbstractComponentAdapter;
import org.exoplatform.container.ConcurrentContainer;
import org.exoplatform.container.ConcurrentContainer.CreationalContextComponentAdapter;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.component.ComponentLifecycle;
import org.exoplatform.container.component.ComponentPlugin;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.context.ContextManager;
import org.exoplatform.container.context.DefinitionException;
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.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.reflect.Method;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.NormalScope;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.PassivationCapable;
import javax.inject.Provider;
import javax.inject.Scope;
import javax.inject.Singleton;
/**
* @author James Strachan
* @author Mauro Talevi
* @author Jeppe Cramon
* @author Benjamin Mestrallet
* @version $Revision: 1.5 $
*/
public class MX4JComponentAdapter<T> extends AbstractComponentAdapter<T> implements Contextual<T>, PassivationCapable
{
/**
* The prefix of the id
*/
private static final String PREFIX = MX4JComponentAdapter.class.getPackage().getName();
/**
* Serial Version ID
*/
private static final long serialVersionUID = -9001193588034229411L;
protected transient volatile T instance_;
private transient volatile T proxy;
private transient volatile String id;
protected transient final Lock lock;
/**
* Indicates whether or not it should be managed as a singleton
*/
protected volatile boolean isSingleton = true;
protected transient volatile boolean isInitialized;
/**
* The scope of the adapter
*/
protected transient final AtomicReference<Class<? extends Annotation>> scope =
new AtomicReference<Class<? extends Annotation>>();
private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.MX4JComponentAdapter");
/** . */
protected transient final ExoContainer exocontainer;
/** . */
private transient final ConcurrentContainer container;
public MX4JComponentAdapter(ExoContainer holder, ConcurrentContainer container, Object key, Class<T> implementation)
{
this(holder, container, key, implementation, new ReentrantLock());
}
protected MX4JComponentAdapter(ExoContainer holder, ConcurrentContainer container, Object key, Class<T> implementation, Lock lock)
{
super(key, implementation);
this.exocontainer = holder;
this.container = container;
this.lock = lock;
}
public T getComponentInstance()
{
if (instance_ != null)
return instance_;
else if (proxy != null)
return proxy;
if (!exocontainer.isContextManagerLoaded() && ContextManager.class.isAssignableFrom(getComponentImplementation()))
{
return create();
}
ContextManager manager = exocontainer.getContextManager();
if (manager == null)
{
return create();
}
return create(manager, true);
}
private T create(ContextManager manager, boolean retryAllowed)
{
Class<? extends Annotation> scope = getScope(true, false);
if (scope.equals(Unknown.class) || scope.equals(Singleton.class) || scope.equals(Dependent.class)
|| scope.equals(ApplicationScoped.class))
{
return create();
}
final Context ctx = manager.getContext(scope);
if (ctx == null)
{
if (LOG.isTraceEnabled())
{
LOG.trace("The scope {} is unknown, thus we will create the component {} out of a scope context.",
scope.getName(), getComponentImplementation().getName());
}
if (!retryAllowed)
throw new IllegalArgumentException("The scope and the default scope of the class "
+ getComponentImplementation().getName() + " are unknown");
try
{
Class<? extends Annotation> defaultScope = ContainerUtil.getScope(getComponentImplementation(), true);
setScope(scope, defaultScope);
return create(manager, false);
}
catch (DefinitionException e)
{
throw new IllegalArgumentException("The scope of the class " + getComponentImplementation().getName()
+ " is unknown and we cannot get a clear default scope: " + e.getMessage());
}
}
NormalScope normalScope = scope.getAnnotation(NormalScope.class);
if (normalScope != null)
{
// A proxy is expected
if (normalScope.passivating() && !Serializable.class.isAssignableFrom(getComponentImplementation()))
{
throw new IllegalArgumentException("As the scope " + scope.getName()
+ " is a passivating scope, we expect only serializable objects and "
+ getComponentImplementation().getName() + " is not serializable.");
}
try
{
lock.lock();
if (proxy != null)
return proxy;
T result = ContainerUtil.createProxy(getComponentImplementation(), new Provider<T>()
{
public T get()
{
return createInstance(ctx);
}
});
return proxy = result;
}
finally
{
lock.unlock();
}
}
return createInstance(ctx);
}
/**
* Gives the scope of the adapter
*/
public Class<? extends Annotation> getScope()
{
return getScope(false, false);
}
private Class<? extends Annotation> getScope(boolean initializeIfNull, boolean ignoreExplicit)
{
Class<? extends Annotation> scope = this.scope.get();
if (scope == null && initializeIfNull)
{
scope = ContainerUtil.getScope(getComponentImplementation(), ignoreExplicit);
scope = setScope(null, scope);
}
return scope;
}
private Class<? extends Annotation> setScope(Class<? extends Annotation> expect, Class<? extends Annotation> scope)
{
if (scope == null)
{
scope = Unknown.class;
isSingleton = true;
}
else
{
isSingleton = scope.equals(Singleton.class) || scope.equals(ApplicationScoped.class);
}
if (this.scope.compareAndSet(expect, scope))
{
return scope;
}
return this.scope.get();
}
protected T createInstance(Context ctx)
{
T result = ctx.get(this);
if (result != null)
{
return result;
}
try
{
return ctx.get(this, container.<T> addComponentToCtx(getComponentKey()));
}
finally
{
container.removeComponentFromCtx(getComponentKey());
}
}
private T createInstance(final CreationalContextComponentAdapter<T> ctx, final Component component,
final ConfigurationManager manager, final String componentKey, final InitParams params, final boolean debug)
throws Exception
{
try
{
return SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<T>()
{
public T run() throws Exception
{
T instance;
final Class<T> implementationClass = getComponentImplementation();
// Please note that we cannot fully initialize the Object "instance_" before releasing other
// threads because it could cause StackOverflowError due to recursive calls
instance = exocontainer.createComponent(implementationClass, params);
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(instance);
boolean isSingleton = MX4JComponentAdapter.this.isSingleton;
boolean isInitialized = MX4JComponentAdapter.this.isInitialized;
if (debug)
LOG.debug("==> create component : " + instance);
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
boolean isInjectPresent = container.initializeComponent(instance);
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 = MX4JComponentAdapter.this.isSingleton = true;
scope.set(Singleton.class);
}
if (component != null && component.getComponentPlugins() != null)
{
addComponentPlugin(debug, instance, component.getComponentPlugins(), exocontainer);
}
ExternalComponentPlugins ecplugins =
manager == null ? null : manager.getConfiguration().getExternalComponentPlugins(componentKey);
if (ecplugins != null)
{
addComponentPlugin(debug, instance, ecplugins.getComponentPlugins(), exocontainer);
}
// check if component implement the ComponentLifecycle
if (instance instanceof ComponentLifecycle)
{
ComponentLifecycle lc = (ComponentLifecycle)instance;
lc.initComponent(exocontainer);
}
if (!isInitialized)
{
if (isSingleton)
{
instance_ = instance;
}
MX4JComponentAdapter.this.isInitialized = true;
}
return instance;
}
});
}
catch (PrivilegedActionException e)
{
Throwable cause = e.getCause();
if (cause instanceof Exception)
{
throw (Exception)cause;
}
throw new Exception(cause);
}
}
/**
* {@inheritDoc}
*/
public boolean isSingleton()
{
return isInitialized ? isSingleton : (isSingleton = ContainerUtil.isSingleton(getComponentImplementation()));
}
private void addComponentPlugin(boolean debug, final Object component,
List<org.exoplatform.container.xml.ComponentPlugin> plugins, ExoContainer container) throws Exception
{
if (plugins == null)
return;
for (org.exoplatform.container.xml.ComponentPlugin plugin : plugins)
{
try
{
Class<?> pluginClass = ClassLoading.forName(plugin.getType(), this);
ComponentPlugin cplugin = (ComponentPlugin)container.createComponent(pluginClass, plugin.getInitParams());
cplugin.setName(plugin.getName());
cplugin.setDescription(plugin.getDescription());
Class<?> clazz = component.getClass();
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() + "'.");
continue;
}
final Object[] params = {cplugin};
SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<Void>()
{
public Void run() throws Exception
{
m.invoke(component, params);
return null;
}
});
if (debug)
LOG.debug("==> add component plugin: " + cplugin);
cplugin.setName(plugin.getName());
cplugin.setDescription(plugin.getDescription());
}
catch (Exception ex)
{
LOG.error(
"Failed to instanciate plugin " + plugin.getName() + " for component " + component + ": "
+ ex.getMessage(), ex);
}
}
}
/**
* Finds the best "set method" according to the given method name and type of plugin
* @param clazz the {@link Class} of the target component
* @param name the name of the method
* @param pluginClass the {@link Class} of the plugin
* @return the "set method" corresponding to the given context
*/
protected static Method getSetMethod(Class<?> clazz, String name, Class<?> pluginClass)
{
Method[] methods = clazz.getMethods();
Method bestCandidate = null;
int depth = -1;
for (Method m : methods)
{
if (name.equals(m.getName()))
{
Class<?>[] types = m.getParameterTypes();
if (types != null && types.length == 1 && ComponentPlugin.class.isAssignableFrom(types[0])
&& types[0].isAssignableFrom(pluginClass))
{
int currentDepth = getClosestMatchDepth(pluginClass, types[0]);
if (currentDepth == 0)
{
return m;
}
else if (depth == -1 || depth > currentDepth)
{
bestCandidate = m;
depth = currentDepth;
}
}
}
}
return bestCandidate;
}
/**
* Check if the given plugin class is assignable from the given type, if not we recheck with its parent class
* until we find the closest match.
* @param pluginClass the class of the plugin
* @param type the class from which the plugin must be assignable
* @return The total amount of times we had to up the hierarchy of the plugin
*/
private static int getClosestMatchDepth(Class<?> pluginClass, Class<?> type)
{
return getClosestMatchDepth(pluginClass, type, 0);
}
/**
* Check if the given plugin class is assignable from the given type, if not we recheck with its parent class
* until we find the closest match.
* @param pluginClass the class of the plugin
* @param type the class from which the plugin must be assignable
* @param depth the current amount of times that we had to up the hierarchy of the plugin
* @return The total amount of times we had to up the hierarchy of the plugin
*/
private static int getClosestMatchDepth(Class<?> pluginClass, Class<?> type, int depth)
{
if (pluginClass == null || pluginClass.isAssignableFrom(type))
{
return depth;
}
return getClosestMatchDepth(pluginClass.getSuperclass(), type, depth + 1);
}
/**
* Must be used to create Singleton or Prototype only
*/
protected T create()
{
boolean toBeLocked = !isInitialized;
try
{
if (toBeLocked)
{
lock.lock();
}
return create(container.<T> addComponentToCtx(getComponentKey()));
}
finally
{
if (toBeLocked)
{
lock.unlock();
}
container.removeComponentFromCtx(getComponentKey());
}
}
/**
* {@inheritDoc}
*/
public T create(CreationalContext<T> creationalContext)
{
//
T instance;
Component component = null;
ConfigurationManager manager;
String componentKey;
InitParams params = null;
boolean debug = false;
CreationalContextComponentAdapter<T> ctx = (CreationalContextComponentAdapter<T>)creationalContext;
try
{
// 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();
// Get the component
Object key = getComponentKey();
if (key instanceof String)
componentKey = (String)key;
else
componentKey = ((Class<?>)key).getName();
manager = exocontainer.getComponentInstanceOfType(ConfigurationManager.class);
component = manager == null ? null : manager.getComponent(componentKey);
if (component != null)
{
params = component.getInitParams();
debug = component.getShowDeployInfo();
}
instance = createInstance(ctx, component, manager, componentKey, params, debug);
if (instance instanceof Startable && exocontainer.canBeStopped())
{
// Start the component if the container is already started
((Startable)instance).start();
}
}
catch (Exception ex)
{
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, ex);
}
return instance;
}
/**
* {@inheritDoc}
*/
public void destroy(T instance, CreationalContext<T> creationalContext)
{
try
{
creationalContext.release();
}
catch (Exception e)
{
LOG.error("Could not destroy the instance " + instance + ": " + e.getMessage());
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((getComponentKey() == null) ? 0 : getComponentKey().hashCode());
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MX4JComponentAdapter<?> other = (MX4JComponentAdapter<?>)obj;
if (getComponentKey() == null)
{
if (other.getComponentKey() != null)
return false;
}
else if (!getComponentKey().equals(other.getComponentKey()))
return false;
return true;
}
/**
* {@inheritDoc}
*/
public String getId()
{
if (id != null)
return id;
StringBuilder sb = new StringBuilder(PREFIX);
Object key = getComponentKey();
String componentKey;
if (key instanceof String)
componentKey = (String)key;
else
componentKey = ((Class<?>)key).getName();
return id = sb.append(componentKey).toString();
}
/**
* Defines the scope of the component
* @return <code>true</code> if the component is considered as a singleton,
* <code>false</code> otherwise.
*/
protected boolean manageScope(boolean isSingleton, boolean isInitialized, boolean hasInjectableConstructor,
boolean isInjectPresent)
{
if (!isInitialized)
{
// The adapter has not been initialized yet
if (isInjectPresent || hasInjectableConstructor)
{
// The component is JSR 330 compliant, so we expect the new behavior
Class<? extends Annotation> currentScope = scope.get();
if (currentScope == null)
{
// The scope has not been set which means that the Context Manager has not been defined
currentScope = getScope(true, false);
if (!currentScope.equals(Unknown.class) && !currentScope.equals(Singleton.class)
&& !currentScope.equals(Dependent.class) && !currentScope.equals(ApplicationScoped.class))
{
// The context manager has not been defined and the defined scope is not part of the supported ones
// so we will check the default one and reset the scope
scope.compareAndSet(currentScope, null);
currentScope = getScope(true, true);
if (!currentScope.equals(Unknown.class) && !currentScope.equals(Singleton.class)
&& !currentScope.equals(Dependent.class)
&& !currentScope.equals(ApplicationScoped.class))
{
// The context manager has not been defined and the defined default scope is not part of the supported ones
// so we will check the default one and set the scope to unknown
scope.compareAndSet(currentScope, Unknown.class);
currentScope = Unknown.class;
}
}
}
if (currentScope.equals(Unknown.class))
{
// The scope is unknown so far
isSingleton = MX4JComponentAdapter.this.isSingleton = false;
scope.set(Dependent.class);
}
else
{
isSingleton = MX4JComponentAdapter.this.isSingleton;
}
}
else
{
// The old behavior is expected as there the component is not JSR 330 compliant
isSingleton = MX4JComponentAdapter.this.isSingleton = true;
scope.set(Singleton.class);
}
}
return isSingleton;
}
@Scope
@Documented
@Retention(RUNTIME)
private static @interface Unknown {
}
}