/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.config.extension; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.logging.Level; import java.util.logging.Logger; import javax.enterprise.event.Observes; import javax.enterprise.inject.InjectionException; import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.AnnotatedField; import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.InjectionPoint; import javax.enterprise.inject.spi.InjectionTarget; import javax.enterprise.inject.spi.ObserverMethod; import javax.enterprise.inject.spi.ProcessAnnotatedType; import javax.enterprise.inject.spi.ProcessBean; import javax.enterprise.inject.spi.ProcessInjectionTarget; import javax.enterprise.inject.spi.ProcessProducer; import javax.enterprise.inject.spi.Producer; import com.caucho.config.ConfigException; import com.caucho.config.event.AbstractObserverMethod; import com.caucho.config.event.EventManager; import com.caucho.config.inject.BeanBuilder; import com.caucho.config.inject.DefaultLiteral; import com.caucho.config.inject.InjectManager; import com.caucho.config.inject.ManagedBeanImpl; import com.caucho.config.inject.ProducesFieldBean; import com.caucho.config.inject.ProducesMethodBean; import com.caucho.config.program.BeanArg; import com.caucho.config.reflect.BaseType; import com.caucho.inject.LazyExtension; import com.caucho.inject.Module; import com.caucho.loader.DynamicClassLoader; import com.caucho.util.ConcurrentArrayList; import com.caucho.util.IoUtil; import com.caucho.util.L10N; import com.caucho.vfs.ReadStream; import com.caucho.vfs.Vfs; /** * Manages custom extensions for the inject manager. */ @Module public class ExtensionManager { private static final L10N L = new L10N(ExtensionManager.class); private static final Logger log = Logger.getLogger(ExtensionManager.class.getName()); private final InjectManager _cdiManager; private String _classLoaderHash = ""; private HashSet<URL> _extensionSet = new HashSet<URL>(); private HashMap<Class<?>,ExtensionItem> _extensionMap = new HashMap<Class<?>,ExtensionItem>(); private ConcurrentArrayList<PendingEvent> _pendingEventList = new ConcurrentArrayList<PendingEvent>(PendingEvent.class); private boolean _isCustomExtension; public ExtensionManager(InjectManager cdiManager) { _cdiManager = cdiManager; } boolean isCustomExtension() { return _isCustomExtension; } public void updateExtensions() { try { ClassLoader loader = _cdiManager.getClassLoader(); if (loader == null) return; String hash = DynamicClassLoader.getHash(loader); if (_classLoaderHash.equals(hash)) return; _classLoaderHash = hash; Enumeration<URL> e = loader.getResources("META-INF/services/" + Extension.class.getName()); while (e.hasMoreElements()) { URL url = (URL) e.nextElement(); if (_extensionSet.contains(url)) continue; _extensionSet.add(url); InputStream is = null; try { is = url.openStream(); ReadStream in = Vfs.openRead(is); String line; while ((line = in.readLine()) != null) { int p = line.indexOf('#'); if (p >= 0) line = line.substring(0, p); line = line.trim(); if (line.length() > 0) { loadExtension(line); } } in.close(); } catch (IOException e1) { log.log(Level.WARNING, e1.toString(), e1); } finally { IoUtil.close(is); } } } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } } public void createExtension(String className) { try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class<?> cl = Class.forName(className, false, loader); Constructor<?> ctor = cl.getConstructor(new Class[] { InjectManager.class }); Extension extension = (Extension) ctor.newInstance(_cdiManager); addExtension(extension); } catch (Exception e) { log.log(Level.FINEST, e.toString(), e); } } void loadExtension(String className) { try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class<?> cl = Class.forName(className, false, loader); if (! Extension.class.isAssignableFrom(cl)) throw new InjectionException(L.l("'{0}' is not a valid extension because it does not implement {1}", cl, Extension.class.getName())); Extension extension = null; for (Constructor<?> ctor : cl.getDeclaredConstructors()) { if (ctor.getParameterTypes().length == 0) { ctor.setAccessible(true); extension = (Extension) ctor.newInstance(); } } if (extension == null) extension = (Extension) cl.newInstance(); addExtension(extension); } catch (Exception e) { log.log(Level.WARNING, e.toString(), e); } } public void addExtension(Extension ext) { if (log.isLoggable(Level.FINER)) log.finer(this + " add extension " + ext); // Register extension instance with CDI. // TODO Is this correct or should a managed bean be created? BeanBuilder<? extends Extension> beanBuilder = _cdiManager .createBeanFactory(ext.getClass()); // TODO Is singleton equivalent to ApplicationScoped? That is what the // extension is supposed to be as per CDI specification. _cdiManager.addBean(beanBuilder.singleton(ext)); ExtensionItem item = introspect(ext.getClass()); for (ExtensionMethod method : item.getExtensionMethods()) { Method javaMethod = method.getMethod(); Class<?> rawType = method.getBaseType().getRawClass(); ExtensionObserver observer; observer = new ExtensionObserver(ext, method.getMethod(), method.getArgs()); _cdiManager.getEventManager().addExtensionObserver(observer, method.getBaseType(), method.getQualifiers()); if ((ProcessAnnotatedType.class.isAssignableFrom(rawType)) && ! javaMethod.isAnnotationPresent(LazyExtension.class)) { _cdiManager.setIsCustomExtension(true); } if ((ProcessBean.class.isAssignableFrom(rawType)) && ! javaMethod.isAnnotationPresent(LazyExtension.class)) { _cdiManager.setIsCustomExtension(true); } if ((ProcessInjectionTarget.class.isAssignableFrom(rawType)) && ! javaMethod.isAnnotationPresent(LazyExtension.class)) { _cdiManager.setIsCustomExtension(true); } if ((ProcessProducer.class.isAssignableFrom(rawType)) && ! javaMethod.isAnnotationPresent(LazyExtension.class)) { _cdiManager.setIsCustomExtension(true); } } } private ExtensionItem introspect(Class<?> cl) { ExtensionItem item = _extensionMap.get(cl); if (item == null) { item = new ExtensionItem(cl); _extensionMap.put(cl, item); } return item; } public <T> Bean<T> processBean(Bean<T> bean, ProcessBean<T> processBean) { InjectManager cdi = _cdiManager; BaseType baseType = cdi.createTargetBaseType(processBean.getClass()); baseType = baseType.fill(cdi.createTargetBaseType(bean.getBeanClass())); _pendingEventList.add(new PendingEvent(processBean, baseType)); /* getEventManager().fireExtensionEvent(processBean, baseType); if (processBean instanceof ProcessBeanImpl<?> && ((ProcessBeanImpl<?>) processBean).isVeto()) return null; else return processBean.getBean(); */ return processBean.getBean(); } @Module public <T> Bean<T> processBean(Bean<T> bean, Annotated ann) { InjectManager cdi = _cdiManager; ProcessBeanImpl<T> event = new ProcessBeanImpl<T>(_cdiManager, bean, ann); BaseType baseType = cdi.createTargetBaseType(event.getClass()); baseType = baseType.fill(cdi.createTargetBaseType(bean.getBeanClass())); _pendingEventList.add(new PendingEvent(event, baseType)); /* getEventManager().fireExtensionEvent(event, baseType); if (event.isVeto()) return null; else return event.getBean(); */ return event.getBean(); } @Module public <T> Bean<T> processManagedBean(ManagedBeanImpl<T> bean, Annotated ann) { InjectManager cdi = _cdiManager; ProcessManagedBeanImpl<T> event = new ProcessManagedBeanImpl<T>(_cdiManager, bean, ann); BaseType baseType = cdi.createTargetBaseType(event.getClass()); baseType = baseType.fill(cdi.createTargetBaseType(bean.getBeanClass())); _pendingEventList.add(new PendingEvent(event, baseType)); /* getEventManager().fireExtensionEvent(event, baseType); if (event.isVeto()) return null; else return event.getBean(); */ return event.getBean(); } @Module public <T,X> Bean<X> processProducerMethod(ProducesMethodBean<T,X> bean) { InjectManager cdi = _cdiManager; ProcessProducerMethodImpl<T,X> event = new ProcessProducerMethodImpl<T,X>(_cdiManager, bean); AnnotatedMethod<? super T> method = bean.getProducesMethod(); Bean<?> producerBean = bean.getProducerBean(); BaseType baseType = cdi.createTargetBaseType(event.getClass()); baseType = baseType.fill(cdi.createTargetBaseType(method.getBaseType()), cdi.createTargetBaseType(producerBean.getBeanClass())); _pendingEventList.add(new PendingEvent(event, baseType)); /* getEventManager().fireExtensionEvent(event, baseType); if (event.isVeto()) return null; else return event.getBean(); */ return event.getBean(); } @Module public <T,X> Bean<X> processProducerField(ProducesFieldBean<T,X> bean) { InjectManager cdi = _cdiManager; ProcessProducerFieldImpl<T,X> event = new ProcessProducerFieldImpl<T,X>(_cdiManager, bean); AnnotatedField<? super T> field = bean.getField(); BaseType baseType = cdi.createTargetBaseType(event.getClass()); baseType = baseType.fill(cdi.createTargetBaseType(field.getBaseType()), cdi.createTargetBaseType(bean.getProducerBean().getBeanClass())); _pendingEventList.add(new PendingEvent(event, baseType)); /* getEventManager().fireExtensionEvent(event, baseType); if (event.isVeto()) return null; else return event.getBean(); */ return event.getBean(); } public void processPendingEvents() { while (_pendingEventList.size() > 0) { PendingEvent event = _pendingEventList.remove(0); getEventManager().fireExtensionEvent(event.getEvent(), event.getType()); } } /** * Processes the discovered InjectionTarget */ public <T> InjectionTarget<T> processInjectionTarget(InjectionTarget<T> target, AnnotatedType<T> annotatedType) { InjectManager cdi = _cdiManager; ProcessInjectionTargetImpl<T> processTarget = new ProcessInjectionTargetImpl<T>(_cdiManager, target, annotatedType); BaseType eventType = cdi.createTargetBaseType(ProcessInjectionTargetImpl.class); eventType = eventType.fill(cdi.createTargetBaseType(annotatedType.getBaseType())); getEventManager().fireExtensionEvent(processTarget, eventType); return (InjectionTarget<T>) processTarget.getInjectionTarget(); } /** * Processes the discovered method producer */ public <X,T> Producer<T> processProducer(AnnotatedMethod<X> producesMethod, Producer<T> producer) { InjectManager cdi = _cdiManager; ProcessProducerImpl<T,X> event = new ProcessProducerImpl<T,X>(producesMethod, producer); AnnotatedType<?> declaringType = producesMethod.getDeclaringType(); Type declaringClass; if (declaringType != null) declaringClass = declaringType.getBaseType(); else declaringClass = producesMethod.getJavaMember().getDeclaringClass(); BaseType eventType = cdi.createTargetBaseType(ProcessProducerImpl.class); eventType = eventType.fill(cdi.createTargetBaseType(producesMethod.getBaseType()), cdi.createTargetBaseType(declaringClass)); getEventManager().fireExtensionEvent(event, eventType); return event.getProducer(); } /** * Processes the discovered method producer */ public <X,T> Producer<T> processProducer(AnnotatedField<X> producesField, Producer<T> producer) { InjectManager cdi = _cdiManager; ProcessProducerImpl<T,X> event = new ProcessProducerImpl<T,X>(producesField, producer); AnnotatedType<X> declaringType = producesField.getDeclaringType(); BaseType eventType = cdi.createTargetBaseType(ProcessProducerImpl.class); eventType = eventType.fill(cdi.createTargetBaseType(producesField.getBaseType()), cdi.createTargetBaseType(declaringType.getBaseType())); getEventManager().fireExtensionEvent(event, eventType); return event.getProducer(); } /** * Processes the observer. */ public <T,X> void processObserver(ObserverMethod<T> observer, AnnotatedMethod<X> method) { ProcessObserverImpl<T,X> event = new ProcessObserverImpl<T,X>(_cdiManager, observer, method); AnnotatedMethod<X> annotatedMethod = event.getAnnotatedMethod(); AnnotatedType<X> declaringType = annotatedMethod.getDeclaringType(); ObserverMethod<T> observerMethod = event.getObserverMethod(); Type observedType = observerMethod.getObservedType(); BaseType eventType = _cdiManager.createTargetBaseType(ProcessObserverImpl.class); eventType = eventType.fill(_cdiManager.createTargetBaseType(observedType), _cdiManager.createTargetBaseType(declaringType.getBaseType())); getEventManager().fireExtensionEvent(event, eventType); } public void fireBeforeBeanDiscovery() { getEventManager().fireExtensionEvent(new BeforeBeanDiscoveryImpl(_cdiManager)); } public void fireAfterBeanDiscovery() { getEventManager().fireExtensionEvent(new AfterBeanDiscoveryImpl(_cdiManager)); } public void fireAfterDeploymentValidation() { AfterDeploymentValidationImpl event = new AfterDeploymentValidationImpl(_cdiManager); getEventManager().fireExtensionEvent(event); /* if (event.getDeploymentProblem() != null) throw ConfigException.create(event.getDeploymentProblem()); */ } /** * Creates a discovered annotated type. */ public <T> AnnotatedType<T> processAnnotatedType(AnnotatedType<T> type) { InjectManager cdi = _cdiManager; ProcessAnnotatedTypeImpl<T> processType = new ProcessAnnotatedTypeImpl<T>(type); BaseType baseType = cdi.createTargetBaseType(ProcessAnnotatedTypeImpl.class); baseType = baseType.fill(cdi.createTargetBaseType(type.getBaseType())); getEventManager().fireExtensionEvent(processType, baseType); if (processType.isVeto()) { return null; } type = processType.getAnnotatedType(); return type; } private EventManager getEventManager() { return _cdiManager.getEventManager(); } public String toString() { return getClass().getSimpleName() + "[" + _cdiManager + "]"; } class ExtensionItem { private ArrayList<ExtensionMethod> _observers = new ArrayList<ExtensionMethod>(); ExtensionItem(Class<?> cl) { for (Method method : cl.getDeclaredMethods()) { ExtensionMethod extMethod = bindObserver(cl, method); if (extMethod != null) _observers.add(extMethod); } } private ArrayList<ExtensionMethod> getExtensionMethods() { return _observers; } @SuppressWarnings("unchecked") private ExtensionMethod bindObserver(Class<?> cl, Method method) { Type []param = method.getGenericParameterTypes(); if (param.length < 1) return null; Annotation [][]paramAnn = method.getParameterAnnotations(); if (! hasObserver(paramAnn)) return null; InjectManager inject = _cdiManager; BeanArg<?> []args = new BeanArg[param.length]; for (int i = 1; i < param.length; i++) { Annotation []bindings = inject.getQualifiers(paramAnn[i]); if (bindings.length == 0) bindings = new Annotation[] { DefaultLiteral.DEFAULT }; InjectionPoint ip = null; args[i] = new BeanArg(inject, param[i], bindings, ip); } BaseType baseType = inject.createTargetBaseType(param[0]); return new ExtensionMethod(method, baseType, inject.getQualifiers(paramAnn[0]), args); } private boolean hasObserver(Annotation [][]paramAnn) { for (int i = 0; i < paramAnn.length; i++) { for (int j = 0; j < paramAnn[i].length; j++) { if (paramAnn[i][j].annotationType().equals(Observes.class)) return true; } } return false; } } static class ExtensionMethod { private final Method _method; private final BaseType _type; private final Annotation []_qualifiers; private final BeanArg<?> []_args; ExtensionMethod(Method method, BaseType type, Annotation []qualifiers, BeanArg<?> []args) { _method = method; method.setAccessible(true); _type = type; _qualifiers = qualifiers; _args = args; } public Method getMethod() { return _method; } public BeanArg<?> []getArgs() { return _args; } public BaseType getBaseType() { return _type; } public Annotation []getQualifiers() { return _qualifiers; } public String toString() { return getClass().getSimpleName() + "[" + _method + "]"; } } static class ExtensionObserver extends AbstractObserverMethod<Object> { private Extension _extension; private Method _method; private BeanArg<?> []_args; ExtensionObserver(Extension extension, Method method, BeanArg<?> []args) { _extension = extension; _method = method; _args = args; } public void notify(Object event) { try { Object []args = new Object[_args.length]; args[0] = event; for (int i = 1; i < args.length; i++) { args[i] = _args[i].eval(null); } _method.invoke(_extension, args); } catch (RuntimeException e) { throw e; } catch (InvocationTargetException e) { String loc = (_extension + "." + _method.getName() + ": "); Throwable cause = e.getCause(); if (cause instanceof ConfigException) throw (ConfigException) cause; throw new InjectionException(loc + cause.getMessage(), cause); } catch (Exception e) { String loc = (_extension + "." + _method.getName() + ": "); throw new InjectionException(loc + e.getMessage(), e); } } public String toString() { return getClass().getSimpleName() + "[" + _extension + "," + _method.getName() + "]"; } } static class PendingEvent { private Object _event; private BaseType _type; PendingEvent(Object event, BaseType type) { _event = event; _type = type; } public Object getEvent() { return _event; } public BaseType getType() { return _type; } } }