/* * JBoss, Home of Professional Open Source * Copyright 2010, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.arquillian.impl.core; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import org.jboss.arquillian.impl.core.context.ApplicationContextImpl; import org.jboss.arquillian.impl.core.spi.EventContext; import org.jboss.arquillian.impl.core.spi.EventPoint; import org.jboss.arquillian.impl.core.spi.Extension; import org.jboss.arquillian.impl.core.spi.InjectionPoint; import org.jboss.arquillian.impl.core.spi.InvocationException; import org.jboss.arquillian.impl.core.spi.Manager; import org.jboss.arquillian.impl.core.spi.NonManagedObserver; import org.jboss.arquillian.impl.core.spi.ObserverMethod; import org.jboss.arquillian.impl.core.spi.context.ApplicationContext; import org.jboss.arquillian.impl.core.spi.context.Context; import org.jboss.arquillian.spi.core.Injector; import org.jboss.arquillian.spi.core.event.ManagerStarted; /** * ManagerImpl * * @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a> * @version $Revision: $ */ public class ManagerImpl implements Manager { //-------------------------------------------------------------------------------------|| // Instance Members -------------------------------------------------------------------|| //-------------------------------------------------------------------------------------|| public static Boolean DEBUG = false; private ThreadLocal<Stack<Object>> eventStack; /* * Hack: * Events can be fired nested. If a nested handler throws a exception, the exception is fired on the bus for handling. * It is up to the exception handler to re-throw the exception if it can't handle it. When the re-throw happens * the higher level event will get the exception and re-fire it on the bus. We need to keep track of which exceptions * has been handled in the call chain so we can re-throw without re-firing on a higher level. */ private ThreadLocal<Set<Class<? extends Throwable>>> handledThrowables = new ThreadLocal<Set<Class<? extends Throwable>>>() { @Override protected Set<Class<? extends Throwable>> initialValue() { return new HashSet<Class<? extends Throwable>>(); } }; private List<Context> contexts; private List<Extension> extensions; ManagerImpl(List<Class<? extends Context>> contextClasses, List<Class<?>> extensionClasses) { this.contexts = new ArrayList<Context>(); this.extensions = new ArrayList<Extension>(); try { List<Extension> createdExtensions = createExtensions(extensionClasses); List<Context> createdContexts = createContexts(contextClasses); createApplicationContextAndActivate(); this.contexts.addAll(createdContexts); this.extensions.addAll(createdExtensions); addContextsToApplicationScope(); fire(new ManagerStarted()); } catch (Exception e) { throw new RuntimeException("Could not create and startup manager", e); } } //-------------------------------------------------------------------------------------|| // Required Implementations - Manager -------------------------------------------------|| //-------------------------------------------------------------------------------------|| @Override public void fire(Object event) { fire(event, null); } /* (non-Javadoc) * @see org.jboss.arquillian.impl.core.spi.Manager#fire(java.lang.Object, org.jboss.arquillian.impl.core.spi.NonManagedObserver) */ @Override public <T> void fire(T event, NonManagedObserver<T> nonManagedObserver) { Validate.notNull(event, "Event must be specified"); debug(event, true); // we start fresh pr new event handledThrowables.get().clear(); List<ObserverMethod> observers = resolveObservers(event.getClass()); List<ObserverMethod> interceptorObservers = resolveInterceptorObservers(event.getClass()); try { new EventContextImpl<T>(this, interceptorObservers, observers, nonManagedObserver, event).proceed(); } catch (Exception e) { Throwable fireException = e; if(fireException instanceof InvocationException) { fireException = fireException.getCause(); } if(handledThrowables.get().contains(fireException.getClass())) { UncheckedThrow.throwUnchecked(fireException); } else { fireException(fireException); } } finally { debug(event, false); } } @Override public <T> void bind(Class<? extends Annotation> scope, Class<T> type, T instance) { Validate.notNull(scope, "Scope must be specified"); Validate.notNull(type, "Type must be specified"); Validate.notNull(instance, "Instance must be specified"); Context scopedContext = getScopedContext(scope); if(scopedContext == null) { throw new IllegalArgumentException("No Context registered with support for scope: " + scope); } if(!scopedContext.isActive()) { throw new IllegalArgumentException("No active " + scope.getSimpleName() + " Context to bind to"); } scopedContext.getObjectStore().add(type, instance); } @Override public <T> T resolve(Class<T> type) { Validate.notNull(type, "Type must be specified"); List<Context> activeContexts = resolveActiveContexts(); for(int i = activeContexts.size() -1; i >= 0; i--) { Context context = activeContexts.get(i); T object = context.getObjectStore().get(type); if(object != null) { return object; } } return null; } @Override public void inject(Object obj) { inject(ExtensionImpl.of(obj)); } /* (non-Javadoc) * @see org.jboss.arquillian.spi.Manager#getContext(java.lang.Class) */ @Override public <T> T getContext(Class<T> type) { for(Context context : contexts) { if(type.isInstance(context)) { return type.cast(context); } } return null; } //-------------------------------------------------------------------------------------|| // Exposed Convenience Impl Methods ---------------------------------------------------|| //-------------------------------------------------------------------------------------|| /** * @param <T> * @param scope * @param type * @param instance */ public <T> void bindAndFire(Class<? extends Annotation> scope, Class<T> type, T instance) { bind(scope, type, instance); fire(instance); } /** * @return the extensions */ public <T> T getExtension(Class<T> type) { for(Extension extension : extensions) { Object target = ((ExtensionImpl)extension).getTarget(); if(type.isInstance(target)) { return type.cast(target); } } return null; } /* (non-Javadoc) * @see org.jboss.arquillian.core.spi.Manager#shutdown() */ @Override public void shutdown() { synchronized (this) { for(Context context : contexts) { context.clearAll(); } contexts.clear(); extensions.clear(); if(eventStack != null) { eventStack.remove(); } handledThrowables.remove(); } } //-------------------------------------------------------------------------------------|| // Internal Helper Methods ------------------------------------------------------------|| //-------------------------------------------------------------------------------------|| boolean isExceptionHandled(Throwable e) { return handledThrowables.get().contains(e.getClass()); } void fireException(Throwable event) { debug(event, true); try { List<ObserverMethod> observers = resolveObservers(event.getClass()); if(observers.size() == 0) // no one is handling this Exception, throw it out. { UncheckedThrow.throwUnchecked(event); } for(int i = 0; i < observers.size(); i++) { ObserverMethod observer = observers.get(i); try { debug(observer, false); observer.invoke(this, event); } catch (Exception e) { // getCause(InocationTargetException).getCause(RealCause); Throwable toBeFired = e.getCause(); // same type of exception being fired as caught and is the last observer, throw to avoid loop if(toBeFired.getClass() == event.getClass()) { // on throw if this is the last Exception observer if(i == observers.size()-1) { handledThrowables.get().add(toBeFired.getClass()); // this will throw checked exception if any, and will break the declaration of fire(), will throw the original cause UncheckedThrow.throwUnchecked(toBeFired); } } else { // a new exception was raised, throw fireException(toBeFired); } } } } finally { debug(event, false); } } /** * @param extensions * @return */ private List<Extension> createExtensions(List<Class<?>> extensionClasses) throws Exception { List<Extension> created = new ArrayList<Extension>(); for(Class<?> extensionClass : extensionClasses) { Extension extension = ExtensionImpl.of(Reflections.createInstance(extensionClass)); inject(extension); created.add(extension); } return created; } /** * @param contexts2 * @return */ private List<Context> createContexts(List<Class<? extends Context>> contextClasses) throws Exception { List<Context> created = new ArrayList<Context>(); for(Class<? extends Context> contextClass : contextClasses) { created.add(Reflections.createInstance(contextClass)); } return created; } /** * */ private void createApplicationContextAndActivate() { ApplicationContext context = new ApplicationContextImpl(); context.activate(); context.getObjectStore().add(Injector.class, InjectorImpl.of(this)); contexts.add(context); } /** * @param objectStore */ @SuppressWarnings("unchecked") private void addContextsToApplicationScope() { ApplicationContext appContext = getContext(ApplicationContext.class); ObjectStore store = appContext.getObjectStore(); for(Context context : contexts) { store.add((Class<Context>)context.getClass().getInterfaces()[0], context); } } /** * @param eventType * @return */ private List<ObserverMethod> resolveObservers(Class<?> eventType) { List<ObserverMethod> observers = new ArrayList<ObserverMethod>(); for(Extension extension : extensions) { for(ObserverMethod observer : extension.getObservers()) { if(Reflections.getType(observer.getType()).isAssignableFrom(eventType) && !Reflections.isType(observer.getType(), EventContext.class)) { observers.add(observer); } } } Collections.sort(observers); return observers; } private List<ObserverMethod> resolveInterceptorObservers(Class<?> eventType) { List<ObserverMethod> observers = new ArrayList<ObserverMethod>(); for(Extension extension : extensions) { for(ObserverMethod observer : extension.getObservers()) { if(Reflections.isType(observer.getType(), EventContext.class)) { if(Reflections.getType(observer.getType()).isAssignableFrom(eventType)) { observers.add(observer); } } } } Collections.sort(observers); return observers; } private List<Context> resolveActiveContexts() { List<Context> activeContexts = new ArrayList<Context>(); for(Context context : contexts) { if(context.isActive()) { activeContexts.add(context); } } return activeContexts; } private void inject(Extension extension) { injectInstances(extension); injectEvents(extension); } /** * @param extension */ private void injectInstances(Extension extension) { for(InjectionPoint point : extension.getInjectionPoints()) { point.set(InstanceImpl.of(Reflections.getType(point.getType()), point.getScope(), this)); } } /** * @param extension */ private void injectEvents(Extension extension) { for(EventPoint point : extension.getEventPoints()) { point.set(EventImpl.of(Reflections.getType(point.getType()), this)); } } private Context getScopedContext(Class<? extends Annotation> scope) { for(Context context : contexts) { if(context.getScope() == scope) { return context; } } return null; } void debug(ObserverMethod method, boolean interceptor) { if(DEBUG) { System.out.println(calcDebugPrefix() + "(" + (interceptor ? "I":"O") + ") " +method.getMethod().getDeclaringClass().getSimpleName() + "." + method.getMethod().getName()); } } private void debug(Object event, boolean push) { if(DEBUG) { if(eventStack == null) { eventStack = new ThreadLocal<Stack<Object>>() { @Override protected Stack<Object> initialValue() { return new Stack<Object>(); } }; } if(push) { System.out.println(calcDebugPrefix() + "(E) " + event.getClass().getSimpleName()); eventStack.get().push(event); } else { if(!eventStack.get().isEmpty()) { eventStack.get().pop(); } } } } private String calcDebugPrefix() { int size = eventStack.get().size(); StringBuilder sb = new StringBuilder(); for(int i = 0; i < size; i++) { sb.append("\t"); } return sb.toString(); } }