package com.netflix.governator;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.ScopeAnnotation;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.internal.MoreTypes;
import com.google.inject.multibindings.Multibinder;
import com.netflix.governator.annotations.AutoBindSingleton;
import com.netflix.governator.guice.lazy.LazySingletonScope;
import com.netflix.governator.spi.AnnotatedClassScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Set;
import javax.inject.Scope;
import javax.inject.Singleton;
public class AutoBindSingletonAnnotatedClassScanner implements AnnotatedClassScanner {
private static final Logger LOG = LoggerFactory.getLogger(AutoBindSingletonAnnotatedClassScanner.class);
@Override
public Class<? extends Annotation> annotationClass() {
return AutoBindSingleton.class;
}
@Override
public <T> void applyTo(Binder binder, Annotation annotation, Key<T> key) {
AutoBindSingleton abs = (AutoBindSingleton)annotation;
Class clazz = key.getTypeLiteral().getRawType();
if ( Module.class.isAssignableFrom(clazz) ) {
try {
binder.install((Module)clazz.newInstance());
} catch (Exception e) {
binder.addError("Failed to install @AutoBindSingleton module " + clazz.getName());
binder.addError(e);
}
} else {
bindAutoBindSingleton(binder, abs, clazz);
}
}
private void bindAutoBindSingleton(Binder binder, AutoBindSingleton annotation, Class<?> clazz) {
LOG.info("Installing @AutoBindSingleton '{}'", clazz.getName());
LOG.info("***** @AutoBindSingleton for '{}' is deprecated as of 2015-10-10.\nPlease use a Guice module with bind({}.class).asEagerSingleton() instead.\nSee https://github.com/Netflix/governator/wiki/Auto-Binding",
clazz.getName(), clazz.getSimpleName() );
Singleton singletonAnnotation = clazz.getAnnotation(Singleton.class);
if (singletonAnnotation == null) {
LOG.info("***** {} should also be annotated with @Singleton to ensure singleton behavior", clazz.getName());
}
Class<?> annotationBaseClass = getAnnotationBaseClass(annotation);
// Void.class is used as a marker to mean "default" because annotation defaults cannot be null
if ( annotationBaseClass != Void.class ) {
Object foundBindingClass = searchForBaseClass(clazz, annotationBaseClass, Sets.newHashSet());
Preconditions.checkArgument(foundBindingClass != null, String.format("AutoBindSingleton class %s does not implement or extend %s", clazz.getName(), annotationBaseClass.getName()));
if ( foundBindingClass instanceof Class ) {
if ( annotation.multiple() ) {
Multibinder<?> multibinder = Multibinder.newSetBinder(binder, (Class)foundBindingClass);
//noinspection unchecked
applyScope(multibinder
.addBinding()
.to((Class)clazz),
clazz, annotation);
} else {
//noinspection unchecked
applyScope(binder
.withSource(getCurrentStackElement())
.bind((Class)foundBindingClass)
.to(clazz),
clazz, annotation);
}
}
else if ( foundBindingClass instanceof Type ) {
TypeLiteral typeLiteral = TypeLiteral.get((Type)foundBindingClass);
if ( annotation.multiple() ) {
//noinspection unchecked
applyScope(Multibinder.newSetBinder(binder, typeLiteral)
.addBinding()
.to((Class)clazz),
clazz, annotation);
} else {
//noinspection unchecked
applyScope(binder
.withSource(getCurrentStackElement())
.bind(typeLiteral).to(clazz),
clazz, annotation);
}
} else {
binder.addError("Unexpected binding class: " + foundBindingClass);
}
}
else {
Preconditions.checkState(!annotation.multiple(), "@AutoBindSingleton(multiple=true) must have either value or baseClass set");
applyScope(binder
.withSource(getCurrentStackElement())
.bind(clazz),
clazz, annotation);
}
}
private StackTraceElement getCurrentStackElement() {
return Thread.currentThread().getStackTrace()[1];
}
private void applyScope(ScopedBindingBuilder builder, Class<?> clazz, AutoBindSingleton annotation) {
if (hasScopeAnnotation(clazz)) {
// Honor scoped annotations first
} else if (annotation.eager()) {
builder.asEagerSingleton();
} else {
builder.in(LazySingletonScope.get());
}
}
private boolean hasScopeAnnotation(Class<?> clazz) {
Annotation scopeAnnotation = null;
for (Annotation annot : clazz.getAnnotations()) {
if (annot.annotationType().isAnnotationPresent(ScopeAnnotation.class) || annot.annotationType().isAnnotationPresent(Scope.class)) {
Preconditions.checkState(scopeAnnotation == null, "Multiple scopes not allowed");
scopeAnnotation = annot;
}
}
return scopeAnnotation != null;
}
private Class<?> getAnnotationBaseClass(AutoBindSingleton annotation) {
Class<?> annotationValue = annotation.value();
Class<?> annotationBaseClass = annotation.baseClass();
Preconditions.checkState((annotationValue == Void.class) || (annotationBaseClass == Void.class), "@AutoBindSingleton cannot have both value and baseClass set");
return (annotationBaseClass != Void.class) ? annotationBaseClass : annotationValue;
}
private Object searchForBaseClass(Class<?> clazz, Class<?> annotationBaseClass, Set<Object> usedSet) {
if (clazz == null) {
return null;
}
if (clazz.equals(annotationBaseClass)) {
return clazz;
}
if (!usedSet.add(clazz)) {
return null;
}
for (Type type : clazz.getGenericInterfaces()) {
if (MoreTypes.getRawType(type).equals(annotationBaseClass)) {
return type;
}
}
if (clazz.getGenericSuperclass() != null) {
if (MoreTypes.getRawType(clazz.getGenericSuperclass()).equals(annotationBaseClass)) {
return clazz.getGenericSuperclass();
}
}
for (Class<?> interfaceClass : clazz.getInterfaces()) {
Object foundBindingClass = searchForBaseClass(interfaceClass, annotationBaseClass, usedSet);
if (foundBindingClass != null) {
return foundBindingClass;
}
}
return searchForBaseClass(clazz.getSuperclass(), annotationBaseClass, usedSet);
}
}