Thursday, July 24, 2008

Resource dependency injection in Restlet via Guice

(See a more recent posting on this subject.)

I have a body of code that already benefits from Guice dependency injection, and I want to migrate it from a servlet-based architecture to Restlet without losing those benefits. I've had some success, and I figured I'd report on it.

I don't mean that I wanted to use Guice to wire up an object graph of Restlets (Components, VirtualHosts, Applications, etc.). It's straightforward enough to use the builder-like API of Restlet, and such code is nicely confined in a few places where it doesn't bother the meat of the application, its resources.

What I do want is to have those resources created by Guice, so that they can be constructed with dependencies injected into them. With the Finder approach, resource constructors get a Context, a Request, and a Response, but that's it. I suppose I could find a way to stash an Injector in one of these so that resources could look up the dependencies they need, but I wanted something more direct. I came up with the following scheme, which seems to be working pretty well:

I use a custom Finder that knows how to look up a Handler/Resource by class or Guice key -- instances of these custom Finders are provided by a FinderFactory:
public interface FinderFactory {
    Finder finderFor(Key<? extends Handler> key);
    Finder finderFor(Class<? extends Handler> cls);
}

The class RestletGuice has static methods named createInjector that parallel those of the Guice class. The difference is that it adds bindings for Context, Request, Response, and FinderFactory. In my Guiced version of the FirstStepsApplication from restlet.org, the following lines create an Injector with some bindings and then look up the FinderFactory that will produce Finders that will be able to make use of those bindings. (These bindings are not necessarily good practice; they just demonstrate the technique.)
    Injector injector = RestletGuice.createInjector(new AbstractModule() {
        public void configure() {
            bind(Handler.class)
                .annotatedWith(HelloWorld.class)
                .to(HelloWorldResource.class);
            bindConstant()
                .annotatedWith(named(HelloWorldResource.HELLO_MSG))
                .to("Hello, Restlet-Guice!");
        }
    });
    FinderFactory factory = injector.getInstance(FinderFactory.class);

HelloWorldResource is slightly changed. It has a private final field, msg, that is used to generate the text representation. The msg field is initialized via an injected value.
    @Inject public HelloWorldResource(@Named(HELLO_MSG) String msg,
                                      Request request,
                                      Response response,
                                      Context context) {
        super(context, request, response);
        this.msg = msg;
        getVariants().add(new Variant(MediaType.TEXT_PLAIN));
    }
    static final String HELLO_MSG = "hello.message";

This all comes together in the Application class when attaching a Finder for the default routing.
    Finder finder = factory.finderFor(Key.get(Handler.class, HelloWorld.class));
    Router router = new Router(getContext());
    router.attachDefault(finder);
    return router;

I read this as "Route any request for this application to whatever resource is bound to @HelloWorld-annotated Handlers, injecting that resource's dependencies," which was exactly what I wanted.

For those who don't want to have to call a special RestletGuice method, there is a public class FinderFactoryModule extending AbstractModule that can be used with Guice.createInjector(...) to get the same effect.

FinderFactoryModule also implements FinderFactory, so you can construct an instance in your Restlet wiring code and use it right away. You can then use the FinderFactoryModule to create an injector explicitly.

As a special convenience, if you don't use a FinderFactoryModule to create an injector, one will be created implicitly the first time one of its Finders is used to find a target Handler/Resource. This encourages a style where each Application gets its own implicit Injector by creating a local FinderFactoryModule and using it to create Finders.

That's basically it. It works with plain Guice 1.0 and Restlet 1.1 (since it uses Handler as the base type for all Resources). I haven't had time to package it nicely, but you can follow the links below for the actual code.

Sources (links are out of date, use this instead):
FinderFactory.java
FinderFactoryModule.java
RestletGuice.java

Example (links are out of date, use this instead):
FirstStepsApplication.java
HelloWorld.java
HelloWorldResource.java
Main.java

Update (August 2008)


Chris Lee discovered that Context.getCurrent() doesn't work reliably as of 1.1m5; the workaround is to use Application.getCurrent().getContext(). A more comprehensive fix, that I hope to post soon, would involve letting subclasses of FinderFactoryModule override the methods that create the Providers for Request, Response, and Context.

Update (2009-Feb-3)


Leigh Klotz has a workaround for using Guice Finders with WadlApplication. And I still haven't had time to package any of this more nicely. (Feb 7: Jérôme Louvel took Leigh's suggestion and checked it in on the Restlet trunk.)

6 comments:

Chris Lee said...

Tim, any thoughts on how you would use Guice for a Restlet Filter? I'm thinking of binding it as a Singleton and just using injector.getInstance(FilterClass.class) in my Application setup, but this feels a little ugly.

Tembrel said...

I couldn't come up with a good use case for this. Can you?

Chris Lee said...

Well, what I'm trying to do in this particular case is to inject configuration parameters into a filter so that I can stick certain things onto the request before processing.

After thinking about it a bit more, I can actually inject directly into the resource. Since all my resources extend a base resource, I can do it once there and achieve the same thing as the filter.

Don Xmar said...

Hi,
I converted the app to use Restlet 2.0. There are some big changes since. Now all is fine.

But I have a question - is there a reason why the injector is created on the KeyFinder, the inner class inside the FinderFactoryModule?

Thanks

Tembrel said...

@xmariachi - The intent is that if an injector is not created by the time of the first attempt to inject a resource, one is automatically created using only the FinderFactoryModule. But the code was wrong; it wasn't setting the injector field at all, so each resource injection would create its own injector. I think the fix is to change the line with "inj = Guice.createInjector...)" to "injector = inj = Guice.createInjector". I'll make sure that's right and post the fixed versions as soon as I can. Thanks!

Unknown said...

Hi Tim,

very interesting post. In my current application I chose another - servlet based - approach. Feel free to take a look at it under http://haraldpehl.blogspot.com/2009/11/google-appengine-restlet.html

Cheers Harald