Tapestry Magic #4: Integrating Guice

Whenever someone asks me about the difference between Wicket and Tapestry, I have a lot to say but I always conclude with the statement

In Wicket, it is easy to figure-out what to do but difficult to implement whereas in Tapestry, it might be difficult to figure-out what to do but easy to implement

I knew nothing about Guice till this morning when I thought of today’s post. I had two options for IOC integration example, PicoContainer and Guice. I finally preferred Guice as it is very close to TapestryIOC and so easy to learn. Integrating any IOC with Tapestry is very easy all you have to do is inherit from TapestryFilter and override provideExtraModuleDefs and provide your own ModuleDef

A ModuleDef is a Module definition which is used by the TapestryAppInitializer to create Registry. Whenever Registry has to look for a service it checks with each module definition about what services it provides and when it finds one, it asks for the ServiceDef. ServiceDef is the service definition interface.

The only service that we need for our Guice IOC is the Injector. Once we have it we can get other services from our TapestryIOC. So we begin by creating our Filter


public class TapestryGuiceFilter extends TapestryFilter {
   @Override
   protected ModuleDef[] provideExtraModuleDefs(ServletContext context) {
      return new ModuleDef[] { new GuiceModuleDef(context) };
   }
}

The only thing we have done here is provided our ModuleDef to the Filter. Our ModuleDef is

public class GuiceModuleDef implements ModuleDef {

   private static final String GUICE_MODULES_PARAM = "tapestry-guice-modules";
   private static final String SERVICE_ID = "GuiceInjector";
   private InjectorServiceDef injectorServiceDef;

   public GuiceModuleDef(ServletContext servletContext) {
      final String modulesParam = servletContext
            .getInitParameter(GUICE_MODULES_PARAM);
      if (modulesParam == null) {
         throw new RuntimeException(
               "For Guice Integration, initialization parameter '"
                     + GUICE_MODULES_PARAM + "' must be set in web.xml");
      }

      final String[] moduleNames = TapestryInternalUtils
            .splitAtCommas(modulesParam);
      final Module[] guiceModules = new Module[moduleNames.length];
      for (int i = 0; i < moduleNames.length; ++i) {
         try {
            Class<?> clazz = Class.forName(moduleNames[i]);
            guiceModules[i] = (Module) clazz.newInstance();
         } catch (Exception e) {
            throw new RuntimeException("Could not create guice module: "
                  + moduleNames[i], e);
         }
      }

      Injector injector = Guice.createInjector(guiceModules);
      injectorServiceDef  = new InjectorServiceDef(injector, SERVICE_ID);
   }

   @SuppressWarnings("unchecked")
   public Class getBuilderClass() {
      return null;
   }

   public Set<ContributionDef> getContributionDefs() {
      return Collections.emptySet();
   }

   public Set<DecoratorDef> getDecoratorDefs() {
      return Collections.emptySet();
   }

   public String getLoggerName() {
      return InjectorServiceDef.class.getName();
   }

   public ServiceDef getServiceDef(String serviceId) {
      if(SERVICE_ID.equals(serviceId)){
         return injectorServiceDef;
      }
      return null;
   }

   public Set<String> getServiceIds() {
      return CollectionFactory.newSet(SERVICE_ID);
   }
   
}

This module needs a context parameter tapestry-guice-modules which is a comma separated list of Guice Module classes. In the constructor we get this parameter, split it, load these classes using Class.forName, create instances of each module and finally pass them to Guice.getInjector to initialize Guice. Once we get the injector we create a ServiceDef for it. There is nothing much happening in other methods. This ModuleDef provides only one ServiceDef which is the service definition of Guice’s Injector. This is implemented as under

public class InjectorServiceDef implements ServiceDef {
   
   private Injector injector;
   private String serviceId;

   public InjectorServiceDef(Injector injector, String serviceId){
      this.injector = injector;
      this.serviceId = serviceId;
   }
   
   public ObjectCreator createServiceCreator(
         ServiceBuilderResources resources) {
      return new ObjectCreator(){

         public Object createObject() {
            return injector;
         }
         
      };
   }

   @SuppressWarnings("unchecked")
   public Set<Class> getMarkers() {
      return Collections.emptySet();
   }

   public String getServiceId() {
      return serviceId;
   }

   @SuppressWarnings("unchecked")
   public Class getServiceInterface() {
      return Injector.class;
   }

   public String getServiceScope() {
      return ScopeConstants.DEFAULT;
   }

   public boolean isEagerLoad() {
      return false;
   }
}

This is very simple interface to implement and understand, so I will not elaborate on this.

Now we are done with Injector. We can now access the injector using @InjectService(“GuiceInjector) annotation. Next we need to be able to access every Guice service using our own @org.apache.tapestry5.annotations.Inject. For that we will contribute our own ObjectProvider to the MasterObjectProvider. Our Object Provider is

public class GuiceObjectProvider implements ObjectProvider {

   private Injector injector;

   public GuiceObjectProvider(Injector injector) {
      this.injector = injector;
   }

   @SuppressWarnings("unchecked")
   public <T> T provide(Class<T> objectType,
         AnnotationProvider annotationProvider, ObjectLocator locator) {
      TypeLiteral<?> type = TypeLiteral.get(objectType);
      final List<?> bindings = injector.findBindingsByType(type);
      if (bindings.size() == 1) {
         return injector.getInstance(objectType);
      }

      for (int i = 0; i < bindings.size(); ++i) {
         final Binding binding = (Binding) bindings.get(i);
         Annotation annotation = annotationProvider.getAnnotation(binding
               .getKey().getAnnotationType());
         Key key = Key.get(type, annotation);
         if (key.equals(binding.getKey())) {
            return (T) injector.getInstance(binding.getKey());
         }
      }

      return null;
   }

}

Isn’t Tapestry easy!!. ObjectProvider has to provide an instance given an interface and an AnnotationProvider. Now comes the Guice part of it. We get TypeLiteral for the interface. This gives us full type information about the interface which we pass to the injector’s findBindingsByType. If there is only one such binding type(i.e there is only one implementation bound to an interface), then we use getInstance to create an object. If there are multiple bindings(more than one implementation bound to an interface) then we loop over all of them and use key to match the correct one. Finally we create our Module class

public class TapestryGuiceModule {

   public void contributeMasterObjectProvider(
         @InjectService("GuiceInjector") Injector injector,
         OrderedConfiguration configuration){
      configuration.add("guiceProvider", new GuiceObjectProvider(injector), "after:Service,Alias,Autobuild");
   }

}

Here we are contributing our ObjectProvider to the MasterObjectProvider, a service responsible for providing an implementation based on the interface and associated annotation. That is it, you have a Tapestry-Guice Integration

Tagged: , ,

6 thoughts on “Tapestry Magic #4: Integrating Guice

  1. Howard Lewis Ship April 22, 2011 at 11:15 PM Reply

    The last code example isn’t formatted correctly. Also “We can not access the injector” should be “We can now access the injector”. Finally, it may be more readable if you use fully qualified class names where Tapestry and Guice conflict (i.e., @Inject). Stil, interesting stuff.

  2. tawus April 22, 2011 at 11:41 PM Reply

    Thanks for your comments. Corrected the errors. I have a presentation tomorrow at office but wanted to keep my ‘one post everyday’ going on and hence the typos.
    Your comments keep me going!!

  3. subbu June 30, 2011 at 12:36 AM Reply

    Real good stuff. I love the tapestry framework a lot. Your articles have been at their best making my learning curve from Tapestry4 – to Tapestry5.x a lot easier.

  4. Kai Lilleby February 25, 2012 at 9:00 PM Reply

    when used in combonation with guice-servlet, one should add to GuiceObjectProvider.provide(..) in its very beginning:

    //dont resolve the javax.servlet. types, tapestry handles these
    // (and as of guice 2 – HttpServletRequest/Response is returned as a non-proxied instance –
    // where as tapestry returns a proxied instance always retuning the current request – thats a major difference..)
    if (type.getRawType().getName().startsWith(“javax.servlet.”)) return null;

  5. Christine (@Xtien) March 16, 2014 at 2:55 AM Reply

    I love Tapestry, I know Guice because I’ve been using it on Android for a long time now. Using Guice with Tapestry keeps my world simple 🙂

  6. gvozden March 18, 2014 at 3:24 AM Reply

    In Guice module you should use:

    public class MyGuiceModule extends AbstractModule {

    @Override
    protected void configure() {

    ….

    binder().requireExplicitBindings();
    }
    }

    This will ensure that Guice inject only controlled subset of objects.

Leave a reply to Howard Lewis Ship Cancel reply