package ru.vyarus.dropwizard.guice.module.installer.util;
import com.google.inject.Binder;
import com.google.inject.Injector;
import com.google.inject.binder.ScopedBindingBuilder;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.hk2.utilities.reflection.ParameterizedTypeImpl;
import ru.vyarus.dropwizard.guice.module.installer.feature.jersey.HK2Managed;
import ru.vyarus.dropwizard.guice.module.jersey.support.GuiceComponentFactory;
import ru.vyarus.dropwizard.guice.module.jersey.support.JerseyComponentProvider;
import ru.vyarus.dropwizard.guice.module.jersey.support.LazyGuiceFactory;
import ru.vyarus.java.generics.resolver.GenericsResolver;
import ru.vyarus.java.generics.resolver.context.GenericsContext;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.lang.reflect.Type;
import java.util.List;
/**
* HK binding utilities. Supplement {@link ru.vyarus.dropwizard.guice.module.installer.install.JerseyInstaller}.
*
* @author Vyacheslav Rusakov
* @since 21.11.2014
*/
public final class JerseyBinding {
private JerseyBinding() {
}
/**
* @param type type to check
* @return true if type annotated with {@link HK2Managed}, false otherwise
* @see ru.vyarus.dropwizard.guice.module.installer.feature.jersey.HK2Managed
*/
public static boolean isHK2Managed(final Class<?> type) {
return type.isAnnotationPresent(HK2Managed.class);
}
/**
* Binds component into HK context. If component is annotated with {@link HK2Managed}, then registers type,
* otherwise register guice "bridge" factory around component.
*
* @param binder hk binder
* @param injector guice injector
* @param type component type
* @see ru.vyarus.dropwizard.guice.module.jersey.support.GuiceComponentFactory
*/
public static void bindComponent(final AbstractBinder binder, final Injector injector, final Class<?> type) {
if (isHK2Managed(type)) {
binder.bindAsContract(type).in(Singleton.class);
} else {
// default case: simple service registered directly (including resource)
binder.bindFactory(new GuiceComponentFactory<>(injector, type)).to(type);
}
}
/**
* Binds hk {@link Factory}. If bean is {@link HK2Managed} then registered directly as
* factory. Otherwise register factory through special "lazy bridge" to delay guice factory bean instantiation.
* Also registers factory directly (through wrapper to be able to inject factory by its type).
*
* @param binder hk binder
* @param injector guice injector
* @param type factory to bind
* @param <T> actual type (used to workaround type checks)
* @see ru.vyarus.dropwizard.guice.module.jersey.support.LazyGuiceFactory
* @see ru.vyarus.dropwizard.guice.module.jersey.support.GuiceComponentFactory
*/
@SuppressWarnings("unchecked")
public static <T> void bindFactory(final AbstractBinder binder, final Injector injector, final Class<?> type) {
// resolve Factory<T> actual type to bind properly
final Class<T> res = (Class<T>) GenericsResolver.resolve(type).type(Factory.class).generic(0);
if (isHK2Managed(type)) {
binder.bindFactory((Class<Factory<T>>) type)
.to(res)
.in(Singleton.class);
} else {
binder.bindFactory(new LazyGuiceFactory(injector, type))
.to(res);
// binding factory type to be able to autowire factory by name
binder.bindFactory(new GuiceComponentFactory<>(injector, type))
.to(type);
}
}
/**
* Binds jersey specific component (component implements jersey interface or extends class).
* Specific binding is required for types directly supported by jersey (e.g. ExceptionMapper).
* Such types must be bound to target interface directly, otherwise jersey would not be able to resolve them.
* <p> If type is {@link HK2Managed}, binds directly.
* Otherwise, use guice "bridge" factory to lazily bind type.</p>
*
* @param binder hk binder
* @param injector guice injector
* @param type type which implements specific jersey interface or extends class
* @param specificType specific jersey type (interface or abstract class)
*/
public static void bindSpecificComponent(final AbstractBinder binder, final Injector injector,
final Class<?> type, final Class<?> specificType) {
// resolve generics of specific type
final GenericsContext context = GenericsResolver.resolve(type).type(specificType);
final List<Type> genericTypes = context.genericTypes();
final Type[] generics = genericTypes.toArray(new Type[genericTypes.size()]);
final Type binding = generics.length > 0 ? new ParameterizedTypeImpl(specificType, generics)
: specificType;
if (isHK2Managed(type)) {
binder.bind(type).to(binding).in(Singleton.class);
} else {
// hk cant find different things in different situations, so uniform registration is impossible
if (InjectionResolver.class.equals(specificType)) {
binder.bindFactory(new GuiceComponentFactory<>(injector, type))
.to(type).in(Singleton.class);
binder.bind(type).to(binding).in(Singleton.class);
} else {
binder.bindFactory(new GuiceComponentFactory<>(injector, type))
.to(type).to(binding).in(Singleton.class);
}
}
}
/**
* Used to bind jersey beans in guice context (lazily). Guice context is started first, so there is
* no way to bind instances. Instead "lazy bridge" installed, which will resolve target type on first call.
* Guice is not completely started and direct injector lookup is impossible here, so lazy injector provider used.
*
* @param binder guice binder
* @param provider provider for guice injector
* @param type jersey type to register
* @param <T> type
* @return scoped binder object to optionally define binding scope.
* @see ru.vyarus.dropwizard.guice.injector.lookup.InjectorProvider
*/
public static <T> ScopedBindingBuilder bindJerseyComponent(final Binder binder, final Provider<Injector> provider,
final Class<T> type) {
return binder.bind(type).toProvider(new JerseyComponentProvider<>(provider, type));
}
}