package com.netflix.governator.guice.serviceloader;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.Callable;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
import com.netflix.governator.guice.lazy.LazySingletonScope;
/**
* Simple Guice module to integrate with the {@link ServiceLoader}.
*
* @author elandau
*/
public abstract class ServiceLoaderModule extends AbstractModule {
public interface ServiceBinder<S> {
public ServiceBinder<S> usingClassLoader(ClassLoader classLoader);
public ServiceBinder<S> forInstalledServices(Boolean installed);
public ServiceBinder<S> usingMultibinding(Boolean usingMultibinding);
}
static class ServiceBinderImpl<S> extends AbstractModule implements ServiceBinder<S> {
private final Class<S> type;
private ClassLoader classLoader;
private boolean installed = false;
private boolean asMultibinding = false;
ServiceBinderImpl(Class<S> type) {
this.type = type;
}
@Override
public ServiceBinder<S> usingClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
return this;
}
@Override
public ServiceBinder<S> forInstalledServices(Boolean installed) {
this.installed = installed;
return this;
}
@Override
public ServiceBinder<S> usingMultibinding(Boolean usingMultibinding) {
this.asMultibinding = usingMultibinding;
return this;
}
protected void configure() {
Callable<ServiceLoader<S>> loader;
if (installed) {
if (classLoader != null) {
throw new RuntimeException("Class loader may not be combined with loading installed services");
}
loader = new Callable<ServiceLoader<S>>() {
@Override
public ServiceLoader<S> call() throws Exception {
return ServiceLoader.loadInstalled(type);
}
};
}
else if (classLoader != null) {
loader = new Callable<ServiceLoader<S>>() {
@Override
public ServiceLoader<S> call() throws Exception {
return ServiceLoader.load(type, classLoader);
}
};
}
else {
loader = new Callable<ServiceLoader<S>>() {
@Override
public ServiceLoader<S> call() throws Exception {
return ServiceLoader.load(type);
}
};
}
if (asMultibinding) {
Multibinder<S> binding = Multibinder.newSetBinder(binder(), type);
ServiceLoader<S> services;
try {
for (S service : loader.call()) {
System.out.println("Adding binding for service : " + service.getClass().getName());
ServiceProvider<S> provider = new ServiceProvider<S>(service);
binding.addBinding().toProvider(provider).in(Scopes.SINGLETON);
}
} catch (Exception e) {
throw new ProvisionException("Failed to load services for '" + type + "'", e);
}
}
else {
@SuppressWarnings("unchecked")
TypeLiteral<Set<S>> typeLiteral = (TypeLiteral<Set<S>>) TypeLiteral.get(Types.setOf(type));
bind(typeLiteral)
.toProvider(new ServiceSetProvider<S>(loader))
.in(LazySingletonScope.get());
}
}
}
private final List<ServiceBinderImpl<?>> binders = Lists.newArrayList();
@Override
public final void configure() {
configureServices();
for (ServiceBinderImpl<?> binder : binders) {
install(binder);
}
}
protected abstract void configureServices();
/**
* Load services and make them available via a Set<S> binding using
* multi-binding. Note that this methods loads services lazily but also
* allows for additional bindings to be done via Guice modules.
*
* @param type
*/
public <S> ServiceBinder<S> bindServices(final Class<S> type) {
ServiceBinderImpl<S> binder = new ServiceBinderImpl<S>(type);
binders.add(binder);
return binder;
}
/**
* Custom provider that enables member injection on services
*
* @author elandau
*
* @param <S>
*/
public static class ServiceSetProvider<S> implements ProviderWithExtensionVisitor<Set<S>> {
private Injector injector;
private Callable<ServiceLoader<S>> loader;
public ServiceSetProvider(Callable<ServiceLoader<S>> loader) {
this.loader = loader;
}
@Override
public Set<S> get() {
Set<S> services = Sets.newHashSet();
try {
for (S obj : loader.call()) {
injector.injectMembers(obj);
services.add(obj);
}
} catch (Exception e) {
throw new ProvisionException("Failed to laod services", e);
}
return services;
}
@Override
public <B, V> V acceptExtensionVisitor(
BindingTargetVisitor<B, V> visitor,
ProviderInstanceBinding<? extends B> binding) {
return visitor.visit(binding);
}
@Inject
@Toolable
void initialize(Injector injector) {
this.injector = injector;
}
}
/**
* Custom provider that allows for member injection of a service. Note that while the
* service was instantiated at binding time the members injection won't happen until the
* set of services is injected.
*
* @author elandau
*
* @param <S>
*/
public static class ServiceProvider<S> implements ProviderWithExtensionVisitor<S> {
private S service;
public ServiceProvider(S service) {
this.service = service;
}
@Override
public S get() {
System.out.println("Get : " + service.getClass().getName());
return service;
}
@Override
public <B, V> V acceptExtensionVisitor(
BindingTargetVisitor<B, V> visitor,
ProviderInstanceBinding<? extends B> binding) {
return visitor.visit(binding);
}
@Inject
@Toolable
void initialize(Injector injector) {
injector.injectMembers(service);
}
}
}