/* * 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 { } }