package com.maxifier.guice.lifecycle;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.internal.LinkedBindingImpl;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.InstanceBinding;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderKeyBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* This class provide methods for stop Injector and call @PreDestroy methods for all provides scopes. <br>
* By default you will call @PreDestroy methods only for Singletons.
* <p/>
* For example, if you have injector in work, and want to call @PreDestroy method for all injector Singletons,
* just call Lifecycle.destroy(injector)
* <p/>
* <p/>
* <p/>
* Project: X-Guice
* Date: 17.09.2009
* Time: 14:56:19
* <p/>
* Copyright (c) 1999-2009 Magenta Corporation Ltd. All Rights Reserved.
* Magenta Technology proprietary and confidential.
* Use is subject to license terms.
*
* @author Aleksey Didik
*/
public final class Lifecycle {
private static final Logger logger = LoggerFactory.getLogger(Lifecycle.class);
private Lifecycle() {
}
/**
* Destroy all toInstance bindings and all bindings in scopes, listed
* in <i>inScopes<i>.
* <br> Bindings in Scopes.NO_SCOPE will not be destroyed.
*
* @param injector injector to destroy
* @return Errors object with destroy process errors
*/
public static Errors destroy(Injector injector) {
return destroy(injector, Scopes.SINGLETON);
}
/**
* Destroy all toInstance bindings and all bindings in scopes, listed
* in <i>inScopes<i>.
* <br> Bindings in Scopes.NO_SCOPE will not be destroyed.
*
* @param injector injector to destroy
* @param inScopes scopes for destroy
* @return Errors object with destroy process errors
*/
public static Errors destroy(Injector injector, Scope... inScopes) {
return destroy(injector, Arrays.asList(inScopes));
}
/**
* Destroy all toInstance bindings and all bindings in scopes, listed
* in <i>inScopes<i>.
* <br> Bindings in Scopes.NO_SCOPE will not be destroyed.
*
* @param injector injector to destroy
* @param inScopes scopes for destroy
* @return Errors object with destroy process errors
*/
public static Errors destroy(Injector injector, Collection<Scope> inScopes) {
Errors errors = new Errors();
for (Binding<?> binding : injector.getBindings().values()) {
//finish to Instance binding
if (binding instanceof InstanceBinding) {
destroy(((InstanceBinding) binding).getProvider().get(), errors);
}
if (binding instanceof ProviderInstanceBinding) {
destroy(((ProviderInstanceBinding) binding).getUserSuppliedProvider(), errors);
}
//finish Provider and ProviderInstance binding
if (binding instanceof ProviderKeyBinding<?>) {
//noinspection unchecked
Object providerInstance = injector.getInstance(((ProviderKeyBinding) binding).getProviderKey());
destroy(providerInstance, errors);
}
//finish scopes
Scope scope = getLinkedScope(binding);
if (inScopes.contains(scope)) {
destroy(binding.getProvider().get(), errors);
}
}
return errors;
}
private static void destroy(Object destroyable, Errors errors) {
for (Method method : destroyable.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(PreDestroy.class)) {
invokePreDestroy(errors, destroyable, method);
}
}
}
@SuppressWarnings({"ThrowableInstanceNeverThrown"})
private static void invokePreDestroy(Errors errors, Object o, final Method method) {
if (method.getParameterTypes().length > 0) {
errors.addError(o,
new IllegalStateException(String.format("Lifecycle method '%s' must have no parameters.", method)));
} else {
try {
if (!method.isAccessible()) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
method.setAccessible(true);
return null;
}
});
}
method.invoke(o);
} catch (Throwable e) {
errors.addError(o, e);
}
}
}
private static Scope getLinkedScope(Binding<?> binding) {
BindingScopingVisitor<Scope> scoper = new
BindingScopingVisitor<Scope>() {
public Scope visitNoScoping() {
return Scopes.NO_SCOPE;
}
public Scope visitScopeAnnotation(Class<? extends Annotation>
scopeAnnotation) {
throw new IllegalStateException("no annotations allowed here");
}
public Scope visitScope(Scope scope) {
return scope;
}
public Scope visitEagerSingleton() {
return Scopes.SINGLETON;
}
};
do {
Scope scope = binding.acceptScopingVisitor(scoper);
if (scope != Scopes.NO_SCOPE) {
return scope;
}
if (binding instanceof LinkedBindingImpl) {
LinkedBindingImpl<?> linkedBinding = (LinkedBindingImpl)
binding;
Injector injector = linkedBinding.getInjector();
if (injector != null) {
binding =
injector.getBinding(linkedBinding.getLinkedKey());
continue;
}
}
return Scopes.NO_SCOPE;
} while (true);
}
public static class Errors {
private Map<Object, Throwable> errors = new HashMap<Object, Throwable>();
@SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
private void addError(Object o, Throwable error) {
errors.put(o, error);
}
@SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
public void print() {
if (errors.isEmpty()) {
return;
}
StringBuilder sb = new StringBuilder("");
sb.append("Lifecycle finish erors:\n");
int i = 1;
for (Map.Entry<Object, Throwable> objectThrowableEntry : errors.entrySet()) {
sb.
append(i++).append(") ").
append("Unable to invoke @PreDestroy method of ").
append(objectThrowableEntry.getKey()).
append(", cause ").append(objectThrowableEntry.getValue());
}
logger.error(sb.toString());
}
public Map<Object, Throwable> getErrorsMap() {
return Collections.unmodifiableMap(errors);
}
}
}