package com.netflix.governator.guice.jersey;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.WebApplicationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.ProvisionException;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.servlet.ServletScopes;
import com.google.inject.spi.BindingScopingVisitor;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProvider;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
import com.sun.jersey.core.spi.component.ioc.IoCInstantiatedComponentProvider;
import com.sun.jersey.core.spi.component.ioc.IoCManagedComponentProvider;
import com.sun.jersey.core.spi.component.ioc.IoCProxiedComponentProvider;
/**
* Alternative to Guice's GuiceComponentProviderFactory that does NOT copy Guice bindings into the
* Jersey configuration
*/
final class GovernatorComponentProviderFactory implements IoCComponentProviderFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(GovernatorComponentProviderFactory.class);
private final Map<Scope, ComponentScope> scopeMap = createScopeMap();
private final Injector injector;
/**
* Creates a new GuiceComponentProviderFactory.
*
* @param config the resource configuration
* @param injector the Guice injector
*/
public GovernatorComponentProviderFactory(ResourceConfig config, Injector injector) {
if (injector == null) {
throw new NullPointerException("Guice Injector can not be null!");
}
this.injector = injector;
}
@Override
public IoCComponentProvider getComponentProvider(Class<?> c) {
return getComponentProvider(null, c);
}
@Override
public IoCComponentProvider getComponentProvider(ComponentContext cc, Class<?> clazz) {
LOGGER.info("getComponentProvider({})", clazz.getName());
Key<?> key = Key.get(clazz);
Injector i = findInjector(key);
// If there is no explicit binding
if (i == null) {
// If @Inject is explicitly declared on constructor
if (isGuiceConstructorInjected(clazz)) {
try {
// If a binding is possible
if (injector.getBinding(key) != null) {
LOGGER.info("Binding {} to GuiceInstantiatedComponentProvider", clazz.getName());
return new GuiceInstantiatedComponentProvider(injector, clazz);
}
} catch (ConfigurationException e) {
// The class cannot be injected.
// For example, the constructor might contain parameters that
// cannot be injected
LOGGER.error("Cannot bind " + clazz.getName(), e);
// Guice should have picked this up. We fail-fast to prevent
// Jersey from trying to handle injection.
throw e;
}
// If @Inject is declared on field or method
} else if (isGuiceFieldOrMethodInjected(clazz)) {
LOGGER.info("Binding {} to GuiceInjectedComponentProvider", clazz.getName());
return new GuiceInjectedComponentProvider(injector);
} else {
return null;
}
}
ComponentScope componentScope = getComponentScope(key, i);
LOGGER.info("Binding {} to GuiceManagedComponentProvider with the scope \"{}\"",
new Object[]{clazz.getName(), componentScope});
return new GuiceManagedComponentProvider(i, componentScope, clazz);
}
private ComponentScope getComponentScope(Key<?> key, Injector i) {
return i.getBinding(key).acceptScopingVisitor(new BindingScopingVisitor<ComponentScope>() {
@Override
public ComponentScope visitEagerSingleton() {
return ComponentScope.Singleton;
}
@Override
public ComponentScope visitScope(Scope theScope) {
ComponentScope cs = scopeMap.get(theScope);
return (cs != null) ? cs : ComponentScope.Undefined;
}
@Override
public ComponentScope visitScopeAnnotation(Class scopeAnnotation) {
// This method is not invoked for Injector bindings
throw new UnsupportedOperationException();
}
@Override
public ComponentScope visitNoScoping() {
return ComponentScope.PerRequest;
}
});
}
private Injector findInjector(Key<?> key) {
Injector i = injector;
while (i != null) {
if (i.getBindings().containsKey(key)) {
return i;
}
i = i.getParent();
}
return null;
}
/**
* Determine if a class is an implicit Guice component that can be
* instantiated by Guice and the life-cycle managed by Jersey.
*
* @param c the class.
* @return true if the class is an implicit Guice component.
* @deprecated see {@link #isGuiceConstructorInjected(java.lang.Class) }
*/
@Deprecated
public boolean isImplicitGuiceComponent(Class<?> c) {
return isGuiceConstructorInjected(c);
}
/**
* Determine if a class is an implicit Guice component that can be
* instantiated by Guice and the life-cycle managed by Jersey.
*
* @param c the class.
* @return true if the class is an implicit Guice component.
*/
public boolean isGuiceConstructorInjected(Class<?> c) {
for (Constructor<?> con : c.getDeclaredConstructors()) {
if (isInjectable(con)) {
return true;
}
}
return false;
}
/**
* Determine if a class uses field or method injection via Guice
* using the {@code Inject} annotation
*
* @param c the class.
* @return true if the class is an implicit Guice component.
*/
public boolean isGuiceFieldOrMethodInjected(Class<?> c) {
for (Method m : c.getDeclaredMethods()) {
if (isInjectable(m)) {
return true;
}
}
for (Field f : c.getDeclaredFields()) {
if (isInjectable(f)) {
return true;
}
}
return !c.equals(Object.class) && isGuiceFieldOrMethodInjected(c.getSuperclass());
}
private static boolean isInjectable(AnnotatedElement element) {
return (element.isAnnotationPresent(com.google.inject.Inject.class)
|| element.isAnnotationPresent(javax.inject.Inject.class));
}
/**
* Maps a Guice scope to a Jersey scope.
*
* @return the map
*/
public Map<Scope, ComponentScope> createScopeMap() {
Map<Scope, ComponentScope> result = new HashMap<Scope, ComponentScope>();
result.put(Scopes.SINGLETON, ComponentScope.Singleton);
result.put(Scopes.NO_SCOPE, ComponentScope.PerRequest);
result.put(ServletScopes.REQUEST, ComponentScope.PerRequest);
return result;
}
private static class GuiceInjectedComponentProvider
implements IoCProxiedComponentProvider {
private final Injector injector;
public GuiceInjectedComponentProvider(Injector injector) {
this.injector = injector;
}
@Override
public Object getInstance() {
throw new IllegalStateException();
}
@Override
public Object proxy(Object o) {
try {
injector.injectMembers(o);
} catch (ProvisionException e) {
if (e.getCause() instanceof WebApplicationException) {
throw (WebApplicationException)e.getCause();
}
throw e;
}
return o;
}
}
/**
* Guice injects instances while Jersey manages their scope.
*
* @author Gili Tzabari
*/
private static class GuiceInstantiatedComponentProvider
implements IoCInstantiatedComponentProvider {
private final Injector injector;
private final Class<?> clazz;
/**
* Creates a new GuiceManagedComponentProvider.
*
* @param injector the injector
* @param clazz the class
*/
public GuiceInstantiatedComponentProvider(Injector injector, Class<?> clazz) {
this.injector = injector;
this.clazz = clazz;
}
public Class<?> getInjectableClass(Class<?> c) {
return c.getSuperclass();
}
// IoCInstantiatedComponentProvider
@Override
public Object getInjectableInstance(Object o) {
return o;
}
@Override
public Object getInstance() {
try {
return injector.getInstance(clazz);
} catch (ProvisionException e) {
if (e.getCause() instanceof WebApplicationException) {
throw (WebApplicationException)e.getCause();
}
throw e;
}
}
}
/**
* Guice injects instances and manages their scope.
*
* @author Gili Tzabari
*/
private static class GuiceManagedComponentProvider extends GuiceInstantiatedComponentProvider
implements IoCManagedComponentProvider {
private final ComponentScope scope;
/**
* Creates a new GuiceManagedComponentProvider.
*
* @param injector the injector
* @param scope the Jersey scope
* @param clazz the class
*/
public GuiceManagedComponentProvider(Injector injector, ComponentScope scope, Class<?> clazz) {
super(injector, clazz);
this.scope = scope;
}
@Override
public ComponentScope getScope() {
return scope;
}
}
}