Wednesday, May 26, 2010

Wicket and Guice integration: an alternate route...

This post details a slightly different way of integrating Apache Wicket and Google Guice.

Many posts, reference docs and demos I've seen on the Internet so far, do not employ one of my most favorite features in Guice: the ServletModule to configure Wicket's filter. I really like this feature because I really don't like configuration files and the Web Descriptor (a.k.a web.xml) file is one of the most disliked of them all :) but Thanks to Guice this file's annoyance goes almost unnoticed.

So just to make it clear, the purpose of this integration is to bypass wicket's filter configuration in web.xml and put it in code instead.

Before we start here are several links to posts and reference docs about Wicket and Guice integration that are more common (but do use web.xml to config wicket):

Wicket Guice GuiceWebApplicationFactory javadoc
Alastair Maw’s blog - Wicket gets guicy
Wicket examples - Guice
Atomic Gamer dev blog - Wicket, Guice and Warp-Persist

We start with the web.xml file:
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 version="2.5">

 <filter>
  <filter-name>guiceFilter</filter-name>
  <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
 </filter>

 <filter-mapping>
  <filter-name>guiceFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

 <listener>
  <listener-class>com.codeark.wicketguicealt.Bootstrap</listener-class>
 </listener> 
</web-app>
This web.xml only configures Guice filter and a GuiceServletContextListener as per the instructions here.

The Bootstrap class loads our modules:
public final class Bootstrap extends GuiceServletContextListener { 
 @Override
 protected Injector getInjector() {  
  return Guice.createInjector(new MyModule(), new WebModule());
 } 
}
The binding and configuration of Wicket happens in WebModule (a subclass of ServletModule):
public class WebModule extends ServletModule {
 
 @Override
 protected void configureServlets() {
  bind(WebApplication.class).toProvider(WicketGuiceAppProvider.class);
  
  // avoids "Error initializing WicketFilter - you have no <filter-mapping> element..." 
  // IllegalArgumentException
  Map<String, String> params = new HashMap<String, String>();  
  params.put(WicketFilter.FILTER_MAPPING_PARAM, "/*");
  
  filter("/*").through(WicketGuiceFilter.class, params);
 }
} 
In the first line of configureServlets() we bind WebApplication to a provider, this provider will be our real application factory and will do all the work instead of GuiceWebApplicationFactory.

The GuiceWebApplicationFactory implementation requires that the injector will be stashed in the Servlet Context or that modules will be listed in the web.xml in an init-param like this:
<init-param>
        <param-name>module</param-name>
        <param-value>com.company.MyModule,com.company.MyOtherModule</param-value>
</init-param>
I think both options contradicts the real notion of Guice: "Rather than using an external XML file for configuration, Guice modules are written using regular Java code. Java is familiar, works with your IDE, and survives refactoring" and therefor I opted not to use GuiceWebApplicationFactory. Here is the provider we will use:
public class WicketGuiceAppProvider implements Provider<WebApplication> {
 
 private final Injector injector;

 @Inject
 public WicketGuiceAppProvider(Injector injector) {
  this.injector = injector;  
 }
 
 @Override
 public WebApplication get() {
  WicketGuiceApp app = new WicketGuiceApp(injector);  
  return app;
 }
}
It would be awesome if we could call addComponentInstantiationListener(new GuiceComponentInjector(app, injector)) here instead of passing the injector in the constructor (on injecting it) but GuiceComponentInjector uses InjectorHolder in its constructor:
public GuiceComponentInjector(Application app, Injector injector)
{
 app.setMetaData(GuiceInjectorHolder.INJECTOR_KEY, new GuiceInjectorHolder(injector));
 InjectorHolder.setInjector(this);
}
and InjectorHolder has a dependency on the application instance being tied to the current thread (Application.get() uses ThreadLocal):
public static void setInjector(ConfigurableInjector newInjector)
{
 Application application = Application.get();
 application.setMetaData(INJECTOR_KEY, newInjector);
}
Consequently, we can only call addComponentInstantiationListener() after Application.set() is called and the best place to do so is in the init() method of our WebApplication implementation. we could use Application.set() ourselves (tried that and it works fine) but the scary warnings in the javadoc are scary :-), so here is our WicketGuiceApp class with its init() method:
public class WicketGuiceApp extends WebApplication {
 
 private transient Injector injector;
 
 public WicketGuiceApp(Injector injector) {
  this.injector = injector;  
 }
 
 @Override
 protected void init() {  
  addComponentInstantiationListener(new GuiceComponentInjector(this, injector));
 }

 @Override
 public Class<? extends Page> getHomePage() {
  return HomePage.class;
 }
}
Please note that I've marked the Injector as transient. Under the hood, GuiceComponentInjector will handle the Injector's reference serialization and deserialization.

Back to WebModule, the second important thing we do in configureServlets() is to configure the Wicket filter and filter mapping. We are using our own WicketFilter subclass called WicketGuiceFilter in order to utilize our provider and wire it into wicket's IWebApplicationFactory:
@Singleton
public class WicketGuiceFilter extends WicketFilter {

 @Inject private Provider<WebApplication> appsProvider;
 
 @Override
 protected IWebApplicationFactory getApplicationFactory() {
  return new IWebApplicationFactory() {   
   @Override
   public WebApplication createApplication(WicketFilter filter) {    
    return appsProvider.get();
   }
  };
 } 
}
The reason I chose to subclass WicketFilter instead of doing this inside configureServlets():
Map<String, String> params = new HashMap<String, String>();
 
 params.put(WicketFilter.APP_FACT_PARAM, WicketGuiceAppFactory.class.getName());
 filter("/*").through(WicketFilter.class, params);
Is that WicketFilter instantiates the factory via reflection and the prevents Guice from doing its work.

This concludes our integration, would love to hear any comments.

You can download just the full source of this post and web.xml here

OR

You can download a full eclipse dynamic web project with dependencies included, you will need to import it to your workspace and reconfigure a servlet container runtime (such as apache tomcat) before you can build.

OR

Get the above eclipse project via svn or simply browse the code for your convenience here:
https://www.codeark.com/svn/wicket.guice.alt/trunk
This repository is read only for anonymous access.

14 comments:

  1. Yeah nice more the Wicket and Guice way, remember to post it to the wicket user list..

    ReplyDelete
  2. Thanks!

    (Posted to wicket list a few days ago)

    ReplyDelete
  3. Here some propositions :

    1. Don't use a Provider but inject directly the Application created by guice. That way the application can also have injected fields:

    @Singleton
    public class WicketGuiceFilter extends WicketFilter {

    private final WebApplication app;

    @Inject public WicketGuiceFilter(WebApplication app) {
    this.app = app;
    }

    protected IWebApplicationFactory getApplicationFactory() {
    return new IWebApplicationFactory() {
    public WebApplication createApplication(WicketFilter filter) {
    return app;
    }};
    }
    }

    2. If you use WARP, use a pattern to exclude the resources from the PersistenceFilter :

    filterRegex("/*^(?!.*(swf|jpg|jpeg|png|ico|gif|css|js)).*$").through(PersistenceFilter.class);

    ReplyDelete
  4. Hi, thank you for your excellent input...

    1. I like this approach of injecting the app in the constructor, its very functional and clean, however, imo it breaks the semantics of createApplication(), in my mind this method should return a new instance each time its called.

    I'm thinking about changing the provider to an assisted guice factory, so I can bind to different implementations of WebApplication in different project (which your approach supports, by the way)

    2. Awesome tip! I think you should shoot it to the warp list so everyone will enjoy it :)

    ReplyDelete
  5. Hi, nice route!!

    could you tell me how could I inject Shiro filters using this Guice approach?

    thanks

    ReplyDelete
  6. Anonymous18/2/11 00:57

    One question, even using this approach we still need to use org.apache.wicket.guice.GuiceComponentInjector in app init()?

    public class WicketGuiceApp extends WebApplication {
    ...
    protected void init() {
    addComponentInstantiationListener(new GuiceComponentInjector(this, injector));
    }
    ...

    thanks

    ReplyDelete
  7. @Cristiano: Unfortunately I'm not familiar enough with Shiro to provide you with an answer

    @Anonymous: yes ;)

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. Anonymous13/3/11 16:32

    Hi Yaniv, thanks for sharing this... It works nicelly with wicket 1.4.

    But when I tried to do the same with 1.5 it doesn't work yet..

    Have you tried this approach with version 1.5 ?

    thanks

    ReplyDelete
  10. Hi,

    Sorry for the delayed response, I have been a little bit away from the wicket world of pleasure lately :(

    If you could provide more details here, I'll try to get into it more.

    Yaniv

    ReplyDelete
  11. Anonymous1/2/12 17:51

    In the following piece of code:


    params.put(WicketFilter.APP_FACT_PARAM, WicketGuiceAppFactory.class.getName());


    What is WicketGuiceAppFactory? Shouldn't it be WicketGuiceAppProvider?

    Thanks for the post, very useful. For your information, I tried it with Wicket 1.5.4 and it seems to work (except a small serialization error: java.io.NotSerializableException: com.google.inject.internal.InjectorImpl$4...) trying to fix that.

    ReplyDelete
  12. Anonymous1/2/12 18:03

    Found it. In my previous comment, the class should be replaced by GuiceWebApplicationFactory instead... Maybe it's something specific to Wicket 1.5 ?

    HTH

    ReplyDelete
  13. Its been a while and then some, since I dabbled in this code, but if memory serves, you shouldn't be in a situation where you are serializing the Injector, this means you're doing something wrong.

    This post is two years old, many things have changed in both Guice and Wicket since it was written.

    ReplyDelete