package org.opentripplanner.standalone;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.IoCFullyManagedComponentProvider;
/**
* Ultra-minimal implementation of a subset of JSR-330 dependency injection.
* It handles only singleton-scoped objects (not per-request or per-session lifecycles), and
* these must be instantiated/constructed by the caller upon binding.
* It handles only field injection on those objets, not constructor or method parameter
* injection. It does call @PostConstruct annotated methods when injection is complete.
* It does not automatically work out the correct injection/initialization order.
* Injection and post-construct calls will be performed in the order that the bindings are
* added. It creates IoCComponentProviders for injection into Jersey REST resources.
*/
public class OTPComponentProviderFactory implements IoCComponentProviderFactory {
private static final Logger LOG = LoggerFactory.getLogger(OTPComponentProviderFactory.class);
private Map<Class<?>, Object> bindings = new HashMap<Class<?>, Object>();
private List<Object> bindingOrder = new ArrayList<Object>();
private boolean locked = false;
/** Call the given class's 0-arg constructor and bind the resulting instance. */
public void bind(Class<?> klass) {
try {
Constructor<?> ctor = klass.getDeclaredConstructor();
ctor.setAccessible(true);
bind(klass, ctor.newInstance());
} catch (Exception e) {
LOG.error("Unable to invoke 0-arg constructor on {}", klass);
e.printStackTrace();
}
}
public void bind(Class<?> key, Object value) {
if (locked) {
LOG.error("Attempt to add a binding to a completed set of bindings.");
} else {
if (key.isInstance(value)) {
bindings.put(key, value);
bindingOrder.add(value);
} else {
LOG.error("Type mismatch in binding: " + key + " " + value);
}
}
}
@Override
public IoCComponentProvider getComponentProvider(Class<?> c) {
if ( ! bindings.containsKey(c)) {
LOG.debug("Requested component provider for unbound " + c);
return null; // let Jersey internal provider take care of it
}
LOG.debug("Returning component provider for " + c);
return new JerseyComponentProvider(bindings.get(c));
}
/** Ignore the ComponentContext, wrap the 1-arg version. */
@Override
public IoCComponentProvider getComponentProvider(ComponentContext cc, Class<?> c) {
return getComponentProvider(c);
}
/** Call this method after all bindings have been established. */
public void doneBinding() {
locked = true;
injectFields();
invokePostConstructMethods();
}
/** Perform field injection on all bound instances, in the order they were bound. */
private void injectFields() {
LOG.info("Performing field injection on all bound instances.");
for (Object instance : bindingOrder) {
LOG.debug("Performing field injection on class {}", instance.getClass());
for (Field field : instance.getClass().getDeclaredFields()) {
LOG.debug("Considering field {} for injection", field.getName());
if (field.isAnnotationPresent(Inject.class) ||
field.isAnnotationPresent(Autowired.class)) { // since we're still using Spring
Object obj = bindings.get(field.getType());
LOG.debug("Injecting field {} on instance of {}",
field.getName(), instance.getClass());
if (obj != null) {
try {
field.setAccessible(true);
field.set(instance, obj);
} catch (Exception ex) {
LOG.error("Failed to perform field injection: {}", ex.toString());
ex.printStackTrace();
}
} else {
LOG.error("Found no binding for {}", field.getType());
}
}
}
}
}
/** Call all @PostConstruct-annotated methods on bound instances, in the order they were bound. */
private void invokePostConstructMethods() {
// do not look at the binding class (key), but the concrete type of the instance (value)
for (Object instance : bindingOrder) {
// iterate over all declared methods, not just the public ones
for (Method method : instance.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
try {
LOG.info("Calling post-construct {}", method);
method.setAccessible(true);
method.invoke(instance);
} catch (Exception ex) {
LOG.error("Failed to invoke post-construct: {}", ex.toString());
ex.printStackTrace();
}
}
}
}
}
/** Instances of this class are handed off to Jersey. */
public static class JerseyComponentProvider implements IoCFullyManagedComponentProvider {
final Object o;
public JerseyComponentProvider(Object o) {
this.o = o;
}
@Override
public ComponentScope getScope() {
return ComponentScope.Singleton; // we only bind objects in singleton scope
}
@Override
public Object getInstance() {
return o;
}
}
}