/* * 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.util; import javassist.util.proxy.MethodFilter; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import org.exoplatform.commons.utils.ClassLoading; import org.exoplatform.commons.utils.PropertiesLoader; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.commons.utils.Tools; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.configuration.ConfigurationManager; import org.exoplatform.container.context.DefinitionException; import org.exoplatform.container.xml.Component; import org.exoplatform.container.xml.ComponentLifecyclePlugin; import org.exoplatform.container.xml.ContainerLifecyclePlugin; import org.exoplatform.container.xml.Deserializer; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.NormalScope; import javax.enterprise.inject.Stereotype; import javax.enterprise.inject.UnproxyableResolutionException; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Scope; import javax.inject.Singleton; import javax.servlet.ServletContext; /** * @author Tuan Nguyen (tuan08@users.sourceforge.net) * @since Oct 28, 2004 * @version $Id: ContainerUtil.java 9894 2006-10-31 02:52:41Z tuan08 $ */ @SuppressWarnings("rawtypes") public class ContainerUtil { /** The logger. */ private static final Log LOG = ExoLogger.getExoLogger(ContainerUtil.class); private static final class MethodFilterHolder { private static final MethodFilter METHOD_FILTER = new MethodFilter() { public boolean isHandled(Method method) { return !method.getDeclaringClass().equals(Object.class); } }; } private static final Comparator<Constructor<?>> CONSTRUCTOR_COMPARATOR = new Comparator<Constructor<?>>() { public int compare(Constructor<?> o1, Constructor<?> o2) { return o2.getParameterTypes().length - o1.getParameterTypes().length; } }; public static Constructor<?>[] getSortedConstructors(Class<?> clazz) throws NoClassDefFoundError { Constructor<?>[] constructors = clazz.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) { Constructor<?> c = constructors[i]; if (c.isAnnotationPresent(Inject.class)) { // This constructor has the annotation Inject so we will use it return new Constructor<?>[]{c}; } } constructors = clazz.getConstructors(); Arrays.sort(constructors, CONSTRUCTOR_COMPARATOR); return constructors; } /** * Indicates whether or not the given Class has a constructor annotated with Inject */ public static boolean hasInjectableConstructor(Class<?> clazz) { Constructor<?>[] constructors = clazz.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) { Constructor<?> c = constructors[i]; if (c.isAnnotationPresent(Inject.class)) { // There is a constructor annotated with Inject return true; } } return false; } /** * Indicates whether or not the given Class has only a public non argument constructor */ public static boolean hasOnlyEmptyPublicConstructor(Class<?> clazz) { Constructor<?>[] constructors = clazz.getConstructors(); // No constructor annotated with Inject but we have only a no-argument public constructor // In that case it is optional according to the JSR 330 return (constructors.length == 1 && constructors[0].getParameterTypes().length == 0 && Modifier .isPublic(constructors[0].getModifiers())); } /** * Indicates whether or not this class or one of its super class has at least one * Inject annotation on a field or a method */ public static boolean hasFieldOrMethodWithInject(Class<?> clazz) { if (clazz == null || clazz.equals(Object.class)) { return false; } Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { if (fields[i].isAnnotationPresent(Inject.class)) { return true; } } Method[] methods = clazz.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(Inject.class)) { return true; } } return hasFieldOrMethodWithInject(clazz.getSuperclass()); } /** * Gives the scope defined for the given class * @param clazz the class for which we want the scope * @return a class representing the annotation type of the scope * @throws DefinitionException in case the definition of the scope is not correct */ public static Class<? extends Annotation> getScope(Class<?> clazz) throws DefinitionException { return getScope(clazz, false); } /** * Gives the scope defined for the given class * @param clazz the class for which we want the scope * @param ignoreExplicit indicates whether the explicit scope must be ignored * @return a class representing the annotation type of the scope * @throws DefinitionException in case the definition of the scope is not correct */ public static Class<? extends Annotation> getScope(Class<?> clazz, boolean ignoreExplicit) throws DefinitionException { Annotation[] annotations = clazz.getAnnotations(); Class<? extends Annotation> scope = null; Class<? extends Annotation> defaultScope = null; boolean hasStereotype = false; for (int i = 0; i < annotations.length; i++) { Annotation annotation = annotations[i]; Class<? extends Annotation> annotationType = annotation.annotationType(); if (!ignoreExplicit && (annotationType.isAnnotationPresent(Scope.class) || annotationType .isAnnotationPresent(NormalScope.class))) { if (scope != null) { throw new DefinitionException("You cannot set several scopes to the class " + clazz.getName()); } scope = annotationType; } else if (annotationType.isAnnotationPresent(Stereotype.class)) { hasStereotype = true; Annotation[] stereotypeAnnotations = annotationType.getAnnotations(); for (int j = 0; j < stereotypeAnnotations.length; j++) { Annotation stereotypeAnnotation = stereotypeAnnotations[j]; Class<? extends Annotation> stereotypeAnnotationType = stereotypeAnnotation.annotationType(); if (stereotypeAnnotationType.isAnnotationPresent(Scope.class) || stereotypeAnnotationType.isAnnotationPresent(NormalScope.class)) { if (defaultScope != null && !defaultScope.equals(stereotypeAnnotationType)) { throw new DefinitionException("The class " + clazz.getName() + " has stereotypes with different default scope"); } defaultScope = stereotypeAnnotationType; } } } } if (scope != null) return scope; if (defaultScope != null) return defaultScope; if (hasStereotype) { throw new DefinitionException("The class " + clazz.getName() + " has at least one stereotype but doesn't have any scope, please set an explicit scope"); } return null; } /** * Indicates whether or not the given Class is a singleton or as the scope set to ApplicationScoped */ public static boolean isSingleton(Class<?> clazz) { Class<? extends Annotation> scope = getScope(clazz); if (scope != null) { return scope.equals(Singleton.class) || scope.equals(ApplicationScoped.class); } boolean hasInjectableConstructor = hasInjectableConstructor(clazz); boolean hasOnlyEmptyPublicConstructor = hasOnlyEmptyPublicConstructor(clazz); if (!hasInjectableConstructor && !hasOnlyEmptyPublicConstructor) { // There is no constructor JSR 330 compliant so it is the old mode return true; } else if (hasInjectableConstructor) { // There is at least one constructor annotated with Inject so we know that we expect the new mode return clazz.isAnnotationPresent(Singleton.class); } // We have only one public non argument constructor which is compliant with both modes if (hasFieldOrMethodWithInject(clazz)) { // There is at least one field or a method annotated with Inject so we expect the new mode return clazz.isAnnotationPresent(Singleton.class); } return true; } public static Collection<URL> getConfigurationURL(final String configuration) throws Exception { final ClassLoader cl = Thread.currentThread().getContextClassLoader(); Collection c = SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<Collection>() { public Collection run() throws IOException { return Collections.list(cl.getResources(configuration)); } }); Map<String, URL> map = new HashMap<String, URL>(); Iterator i = c.iterator(); String forbiddenSuffix = "WEB-INF/" + configuration; while (i.hasNext()) { URL url = (URL)i.next(); String key = url.toString(); // The content of the WEB-INF folder is part of the CL in JBoss AS 6 so // we have to get rid of it in order to prevent any boot issues if (key.endsWith(forbiddenSuffix)) { continue; } // jboss bug, jboss has a very weird behavior. It copy all the jar files // and // deploy them to a temp dir and include both jars, the one in sar and tmp // dir, // in the class path. It cause the configuration run twice int index1 = key.lastIndexOf("exo-"); int index2 = key.lastIndexOf("exo."); int index = index1 < index2 ? index2 : index1; if (index >= 0) key = key.substring(index); map.put(key, url); } i = map.values().iterator(); // while(i.hasNext()) { // URL url = (URL) i.next() ; // System.out.println("==> Add " + url); // } return map.values(); } public static void addContainerLifecyclePlugin(ExoContainer container, ConfigurationManager conf) { Iterator i = conf.getConfiguration().getContainerLifecyclePluginIterator(); while (i.hasNext()) { ContainerLifecyclePlugin plugin = (ContainerLifecyclePlugin)i.next(); addContainerLifecyclePlugin(container, plugin); } } private static void addContainerLifecyclePlugin(ExoContainer container, ContainerLifecyclePlugin plugin) { try { Class<?> clazz = ClassLoading.forName(plugin.getType(), ContainerUtil.class); org.exoplatform.container.ContainerLifecyclePlugin cplugin = (org.exoplatform.container.ContainerLifecyclePlugin)container .createComponent(clazz, plugin.getInitParams()); cplugin.setName(plugin.getName()); cplugin.setDescription(plugin.getDescription()); container.addContainerLifecylePlugin(cplugin); } catch (Exception ex) { LOG.error("Failed to instanciate plugin " + plugin.getType() + ": " + ex.getMessage(), ex); } } public static void addComponentLifecyclePlugin(ExoContainer container, ConfigurationManager conf) { Collection plugins = conf.getConfiguration().getComponentLifecyclePlugins(); Iterator i = plugins.iterator(); while (i.hasNext()) { ComponentLifecyclePlugin plugin = (ComponentLifecyclePlugin)i.next(); try { Class<?> classType = ClassLoading.loadClass(plugin.getType(), ContainerUtil.class); org.exoplatform.container.component.ComponentLifecyclePlugin instance = (org.exoplatform.container.component.ComponentLifecyclePlugin)classType.newInstance(); container.addComponentLifecylePlugin(instance); } catch (Exception ex) { LOG.error("Failed to instanciate plugin " + plugin.getType() + ": " + ex.getMessage(), ex); } } } public static void addComponents(ExoContainer container, ConfigurationManager conf) { Collection components = conf.getComponents(); if (components == null) return; Iterator i = components.iterator(); while (i.hasNext()) { Component component = (Component)i.next(); String type = component.getType(); String key = component.getKey(); try { Class<?> classType = ClassLoading.loadClass(type, ContainerUtil.class); if (key == null) { if (component.isMultiInstance()) { throw new UnsupportedOperationException("Multi-instance isn't allowed anymore"); } else { container.registerComponentImplementation(classType); } } else { try { Class<?> keyType = ClassLoading.loadClass(key, ContainerUtil.class); if (component.isMultiInstance()) { throw new UnsupportedOperationException("Multi-instance isn't allowed anymore"); } else { container.registerComponentImplementation(keyType, classType); } } catch (Exception ex) { container.registerComponentImplementation(key, classType); } } } catch (ClassNotFoundException ex) { LOG.error("Cannot register the component corresponding to key = '" + key + "' and type = '" + type + "'", ex); } } } /** * Loads the properties file corresponding to the given url * @param url the url of the properties file * @return a {@link Map} of properties */ public static Map<String, String> loadProperties(URL url) { return loadProperties(url, true); } /** * Loads the properties file corresponding to the given url * @param url the url of the properties file * @param resolveVariables indicates if the variables must be resolved * @return a {@link Map} of properties */ public static Map<String, String> loadProperties(URL url, boolean resolveVariables) { LinkedHashMap<String, String> props = null; String path = null; InputStream in = null; try { // if (url != null) { path = url.getPath(); in = url.openStream(); } // if (in != null) { String fileName = url.getFile(); if (Tools.endsWithIgnoreCase(path, ".properties")) { if (LOG.isDebugEnabled()) LOG.debug("Attempt to load property file " + path); props = PropertiesLoader.load(in); } else if (Tools.endsWithIgnoreCase(fileName, ".xml")) { if (LOG.isDebugEnabled()) LOG.debug("Attempt to load property file " + path + " with XML format"); props = PropertiesLoader.loadFromXML(in); } else if (LOG.isDebugEnabled()) { LOG.debug("Will not load property file" + path + " because its format is not recognized"); } if (props != null && resolveVariables) { // Those properties are used for variables resolution final Map<String, Object> currentProps = new HashMap<String, Object>(); for (Map.Entry<String, String> entry : props.entrySet()) { String propertyName = entry.getKey(); String propertyValue = entry.getValue(); propertyValue = Deserializer.resolveVariables(propertyValue, currentProps); props.put(propertyName, propertyValue); currentProps.put(propertyName, propertyValue); } } } else { LOG.error("Could not load property file " + path); } } catch (Exception e) { LOG.error("Cannot load property file " + path, e); } finally { if (in != null) { try { in.close(); } catch (IOException ignore) { if (LOG.isTraceEnabled()) { LOG.trace("An exception occurred: " + ignore.getMessage()); } } } } return props; } /** * Creates a proxy of the given super class whose instance will be created accessed lazily thanks to a provider * @param superClass the super class of the proxy to create * @param provider the provider that will create the instance lazily * @return a proxy of the given super class * @throws UnproxyableResolutionException if any issue occurs while creating the proxy */ public static <T> T createProxy(final Class<T> superClass, final Provider<T> provider) throws UnproxyableResolutionException { PrivilegedExceptionAction<T> action = new PrivilegedExceptionAction<T>() { public T run() throws Exception { // We first make sure that there is no non-static, final methods with public, protected or default visibility Method[] methods = superClass.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { Method m = methods[i]; int modifiers = m.getModifiers(); if (Modifier.isFinal(modifiers) && !Modifier.isPrivate(modifiers) && !Modifier.isStatic(modifiers)) { throw new UnproxyableResolutionException( "Cannot create a proxy for the class " + superClass.getName() + " because it has at least one non-static, final method with public, protected or default visibility"); } } try { ProxyFactory factory = new ProxyFactory(); factory.setSuperclass(superClass); factory.setFilter(MethodFilterHolder.METHOD_FILTER); MethodHandler handler = new ProxyMethodHandler<T>(provider); return superClass.cast(factory.create(new Class<?>[0], new Object[0], handler)); } catch (Exception e) { throw new UnproxyableResolutionException("Cannot create a proxy for the class " + superClass.getName(), e); } } }; try { return SecurityHelper.doPrivilegedExceptionAction(action); } catch (PrivilegedActionException e) { Throwable cause = e.getCause(); if (cause instanceof UnproxyableResolutionException) { throw (UnproxyableResolutionException)cause; } else { throw new UnproxyableResolutionException("Cannot create a proxy for the class " + superClass.getName(), cause); } } } /** * Gives the context name thanks to the method {@link ServletContext#getServletContextName()} if not <code>null</code> * otherwise it will extract the value from the value of {@link ServletContext#getContextPath()} * @param context the context from which we want to extract the name of the context * @return the context name */ public static String getServletContextName(ServletContext context) { String result = context.getServletContextName(); if (result == null) { result = context.getContextPath(); if (result == null) return result; if (result.startsWith("/")) result = result.substring(1); int index = result.indexOf('/'); if (index >= 0) result = result.substring(0, index); } return result; } /** * The method handler used for the dynamic Proxies */ private static class ProxyMethodHandler<T> implements MethodHandler { private final Provider<T> provider; public ProxyMethodHandler(Provider<T> provider) { this.provider = provider; } /** * {@inheritDoc} */ public Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable { if (!m.isAccessible()) m.setAccessible(true); return m.invoke(provider.get(), args); } } }