/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.guice;
import static java.util.Arrays.asList;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.resource.Finder;
import org.restlet.resource.ServerResource;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Stage;
/**
* Guice dependency injection for Restlet.
*
* @author Tim Peierls
*/
public class RestletGuice {
/**
* A Guice module that implements {@link FinderFactory}. On first use of the
* methods of this facility, if the module hasn't been used to create an
* {@link Injector}, this module creates its own Injector.
*/
public static class Module extends AbstractModule implements FinderFactory {
class KeyFinder extends Finder {
private final Class<?> targetClass;
KeyFinder(Type type) {
this.targetClass = (Class<?>) type;
}
@Override
public final Context getContext() {
return getInjector().getInstance(Context.class);
}
protected final Injector getInjector() {
Injector inj = injector;
if (inj == null) {
synchronized (RestletGuice.Module.this) {
inj = injector;
if (inj == null) {
injector = inj = Guice
.createInjector(RestletGuice.Module.this);
}
}
}
return inj;
}
public final Class<? extends ServerResource> getTargetClass() {
// If the key type is a subtype of ServerResource, return it.
Class<ServerResource> src = ServerResource.class;
if (src != null && targetClass != null
&& src.isAssignableFrom(targetClass)) {
@SuppressWarnings("unchecked")
Class<? extends ServerResource> result = (Class<? extends ServerResource>) targetClass;
return result;
}
// Otherwise, we can't in general determine the true target
// type, so we revert to the superclass implementation.
// Since we used the no-arg Finder constructor, it will return
// null unless someone has explicitly set a target class. This
// is only relevant to the use of the Router.detach(Class<?>
// targetClass) method; it implies that we cannot detach routes
// that target dependency-injected resources attached as
// non-ServerResource types without explicitly setting a target
// class type. This seems like a *very* minor restriction.
return super.getTargetClass();
}
}
class ServerResourceKeyFinder extends KeyFinder {
private final Key<?> serverResourceKey;
ServerResourceKeyFinder(Key<?> serverResourceKey) {
super(serverResourceKey.getTypeLiteral().getType());
this.serverResourceKey = serverResourceKey;
}
@Override
public ServerResource create(Request request, Response response) {
try {
return ServerResource.class.cast(getInjector().getInstance(
serverResourceKey));
} catch (ClassCastException ex) {
String msg = String.format(
"Must bind %s to ServerResource (or subclass)",
serverResourceKey);
throw new ProvisionException(msg, ex);
}
}
}
//
// FinderFactory methods
//
private static ThreadLocal<Boolean> alreadyBound = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
@Inject
private volatile Injector injector;
private final Iterable<? extends com.google.inject.Module> modules;
/**
* Creates a RestletGuice.Module that will install the given modules.
*/
public Module(com.google.inject.Module... modules) {
this.modules = asList(modules);
}
/**
* Creates a RestletGuice.Module that will install the given modules.
*/
public Module(Iterable<? extends com.google.inject.Module> modules) {
this.modules = modules;
}
/**
* If this module is used in more than one injector, we clear the
* thread-local boolean that prevents binding more than once in the same
* thread.
*/
@Inject
private void clearAlreadyBound() {
alreadyBound.set(false);
}
@Override
protected final void configure() {
if (injector != null) {
throw new IllegalStateException(
"can't reconfigure with existing Injector");
}
if (!alreadyBound.get()) {
alreadyBound.set(true);
bind(FinderFactory.class).toInstance(this);
bind(Application.class).toProvider(newApplicationProvider());
bind(Context.class).toProvider(newContextProvider());
bind(Request.class).toProvider(newRequestProvider());
bind(Response.class).toProvider(newResponseProvider());
}
for (com.google.inject.Module module : modules) {
install(module);
}
}
public Finder finder(Class<?> cls) {
return new ServerResourceKeyFinder(Key.get(cls));
}
public Finder finder(Class<?> cls, Class<? extends Annotation> qualifier) {
return new ServerResourceKeyFinder(Key.get(cls, qualifier));
}
/**
* Creates a {@link Provider}r for the current {@link Application}.
* Override to use a custom Application provider.
*
* @return A {@link Provider} for the current {@link Application}.
*/
protected Provider<Application> newApplicationProvider() {
return new Provider<Application>() {
public Application get() {
return Application.getCurrent();
}
};
}
/**
* Creates a {@link Provider} for the current {@link Context}. Override
* to use a custom Context provider.
*
* @return A {@link Provider} for the current {@link Context}.
*/
protected Provider<Context> newContextProvider() {
return new Provider<Context>() {
public Context get() {
return Context.getCurrent();
}
};
}
/**
* Creates a {@link Provider} for the current {@link Request}. Override
* to use a custom Request provider.
*
* @return A {@link Provider} for the current {@link Request}.
*/
protected Provider<Request> newRequestProvider() {
return new Provider<Request>() {
public Request get() {
return Request.getCurrent();
}
};
}
/**
* Creates a {@link Provider} for the current {@link Response}. Override
* to use a custom Response provider.
*
* @return A {@link Provider} for the current {@link Response}.
*/
protected Provider<Response> newResponseProvider() {
return new Provider<Response>() {
public Response get() {
return Response.getCurrent();
}
};
}
}
/**
* Creates an instance of {@link Injector} from the given modules with
* {@link FinderFactory} bound to an implementation that uses the injector's
* bindings to create Finder instances.
*
* @param modules
* The list of modules.
* @return The injector for the list of modules.
*/
public static Injector createInjector(com.google.inject.Module... modules) {
return injectorFor(null, new Module(modules));
}
/**
* Creates an instance of {@link Injector} from the given modules with
* {@link FinderFactory} bound to an implementation that uses the injector's
* bindings to create Finder instances.
*
* @param modules
* The collection of modules.
* @return The injector for the list of modules.
*/
public static Injector createInjector(
Iterable<com.google.inject.Module> modules) {
return injectorFor(null, new Module(modules));
}
/**
* Creates an instance of {@link Injector} in the given {@link Stage} from
* the given modules with {@link FinderFactory} bound to an implementation
* that uses the injector's bindings to create {@link Finder} instances.
*
* @param stage
* The {@link Stage}.
* @param modules
* The list of modules.
* @return The injector for the list of modules in the given stage.
*/
public static Injector createInjector(Stage stage,
com.google.inject.Module... modules) {
return injectorFor(stage, new Module(modules));
}
/**
* Creates an instance of {@link Injector} in the given {@link Stage} from
* the given modules with {@link FinderFactory} bound to an implementation
* that uses the injector's bindings to create {@link Finder} instances.
*
* @param stage
* The {@link Stage}.
* @param modules
* The list of modules.
* @return The injector for the list of modules in the given stage.
*/
public static Injector createInjector(Stage stage,
Iterable<com.google.inject.Module> modules) {
return injectorFor(stage, new Module(modules));
}
private static Injector injectorFor(Stage stage, Module rootModule) {
if (stage == null) {
return Guice.createInjector(rootModule);
} else {
return Guice.createInjector(stage, rootModule);
}
}
}