/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.camel.guice.testing; import java.lang.reflect.Modifier; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import com.google.common.base.Preconditions; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import org.apache.camel.guice.inject.Injectors; import org.apache.camel.guice.support.CloseErrors; import org.apache.camel.guice.support.CloseFailedException; import org.apache.camel.guice.support.internal.CloseErrorsImpl; import org.apache.camel.guice.util.CloseableScope; /** * Used to manage the injectors for the various injection points */ public class InjectorManager { private static final String NESTED_MODULE_CLASS = "TestModule"; private Map<Object, Injector> injectors = new ConcurrentHashMap<Object, Injector>(); private AtomicInteger initializeCounter = new AtomicInteger(0); private CloseableScope testScope = new CloseableScope(TestScoped.class); private CloseableScope classScope = new CloseableScope(ClassScoped.class); private boolean closeSingletonsAfterClasses; private boolean runFinalizer = true; private Class<? extends Module> moduleType; public void beforeClasses() { int counter = initializeCounter.incrementAndGet(); if (counter > 1) { System.out.println("WARNING! Initialised more than once! Counter: " + counter); } else { if (runFinalizer) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { closeSingletons(); } catch (Throwable e) { System.out.println("Failed to shut down Guice Singletons: " + e); e.printStackTrace(); } } }); } } } /** Lets close all of the injectors we have created so far */ public void afterClasses() throws CloseFailedException { Injector injector = injectors.get(moduleType); if (injector != null) { classScope.close(injector); } else { System.out.println("Could not close Class scope as there is no Injector for module type"); } // NOTE that we don't have any good hooks yet to call complete() // when the JVM is completed to ensure real singletons shut down // correctly // if (isCloseSingletonsAfterClasses()) { closeInjectors(); } } public void beforeTest(Object test) throws Exception { Preconditions.checkNotNull(test, "test"); Class<? extends Object> testType = test.getClass(); moduleType = getModuleForTestClass(testType); Injector classInjector = injectors.get(moduleType); if (classInjector == null) { classInjector = createInjector(moduleType); Preconditions.checkNotNull(classInjector, "classInjector"); injectors.put(moduleType, classInjector); } injectors.put(testType, classInjector); classInjector.injectMembers(test); } public void afterTest(Object test) throws Exception { Injector injector = injectors.get(test.getClass()); if (injector == null) { System.out.println("Warning - no injector available for: " + test); } else { testScope.close(injector); } } /** * Closes down any JVM level singletons used in this testing JVM */ public void closeSingletons() throws CloseFailedException { closeInjectors(); } public boolean isCloseSingletonsAfterClasses() { return closeSingletonsAfterClasses; } public void setCloseSingletonsAfterClasses(boolean closeSingletonsAfterClasses) { this.closeSingletonsAfterClasses = closeSingletonsAfterClasses; } protected class TestModule extends AbstractModule { protected void configure() { bindScope(ClassScoped.class, classScope); bindScope(TestScoped.class, testScope); } } protected void closeInjectors() throws CloseFailedException { CloseErrors errors = new CloseErrorsImpl(this); Set<Entry<Object, Injector>> entries = injectors.entrySet(); for (Entry<Object, Injector> entry : entries) { Injector injector = entry.getValue(); Injectors.close(injector, errors); } injectors.clear(); errors.throwIfNecessary(); } /** * Factory method to return the module type that will be used to create an * injector. * * The default implementation will use the system property * <code>org.guiceyfruit.modules</code> (see * {@link Injectors#MODULE_CLASS_NAMES} otherwise if that is not set it will * look for the {@link UseModule} annotation and use the module defined on * that otherwise it will try look for the inner public static class * "TestModule" * * @see org.apache.camel.guice.testing.UseModule * @see #NESTED_MODULE_CLASS */ @SuppressWarnings("unchecked") protected Class<? extends Module> getModuleForTestClass(Class<?> objectType) throws IllegalAccessException, InstantiationException, ClassNotFoundException { String modules = System.getProperty(Injectors.MODULE_CLASS_NAMES); if (modules != null) { modules = modules.trim(); if (modules.length() > 0) { System.out.println("Overloading Guice Modules: " + modules); return null; } } Class<? extends Module> moduleType; UseModule config = objectType.getAnnotation(UseModule.class); if (config != null) { moduleType = config.value(); } else { String name = objectType.getName() + "$" + NESTED_MODULE_CLASS; Class<?> type; try { type = objectType.getClassLoader().loadClass(name); } catch (ClassNotFoundException e) { try { type = Thread.currentThread().getContextClassLoader().loadClass(name); } catch (ClassNotFoundException e2) { throw new ClassNotFoundException( "Class " + objectType.getName() + " does not have a @UseModule annotation nor does it have a nested class called " + NESTED_MODULE_CLASS + " available on the classpath. Please see: http://code.google.com/p/guiceyfruit/wiki/Testing" + e, e); } } try { moduleType = (Class<? extends Module>)type; } catch (Exception e) { throw new IllegalArgumentException("Class " + type.getName() + " is not a Guice Module!", e); } } int modifiers = moduleType.getModifiers(); if (Modifier.isAbstract(modifiers) || !Modifier.isPublic(modifiers)) { throw new IllegalArgumentException("Class " + moduleType.getName() + " must be a public class which is non abstract"); } try { moduleType.getConstructor(); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Class " + moduleType.getName() + " must have a zero argument constructor", e); } return moduleType; } /** * Creates the injector for the given key */ protected Injector createInjector(Class<? extends Module> moduleType) throws InstantiationException, IllegalAccessException, ClassNotFoundException { if (moduleType == null) { return Injectors.createInjector(System.getProperties(), new TestModule()); } // System.out.println("Creating Guice Injector from module: " + // moduleType.getName()); Module module = moduleType.newInstance(); return Guice.createInjector(module, new TestModule()); } }