/**
*
* 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.guiceyfruit.testing;
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 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 org.guiceyfruit.Injectors;
import org.guiceyfruit.support.CloseErrors;
import org.guiceyfruit.support.CloseFailedException;
import org.guiceyfruit.support.internal.CloseErrorsImpl;
import org.guiceyfruit.util.CloseableScope;
/**
* Used to manage the injectors for the various injection points
*
* @version $Revision: 1.1 $
*/
public class InjectorManager {
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 static final String NESTED_MODULE_CLASS = "TestModule";
private boolean closeSingletonsAfterClasses = false;
private boolean runFinalizer = true;
private Injector lastClassInjector;
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 " + injector);
}
// 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;
synchronized (injectors) {
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 {
lastClassInjector = injector;
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) {
// Object key = entry.getKey();
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.guiceyfruit.testing.UseModule
* @see #NESTED_MODULE_CLASS
*/
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());
}
}