Overview

Using Weld CDI with Vaadin and Push

No Comments

At codecentric we like to use the Java and GWT framework Vaadin for creation of web applications following the MVP pattern.
While it features a AWT/SWT like programming model, it is however lacking built-in support for a CDI framework, which is strongly recommended to inject view components and to send events to presenters using an event bus.
Additionally Vaadin applications are usually deployed on Jetty and Tomcat, web servers which do not come with built-in CDI support as well.

There are two parts needed to enable dependency injection and events in Vaadin:

  • Add CDI support to the containers not having it.
  • Add CDI support to Vaadin.

CDI and Tomcat

Adding CDI to containers is easy. In this post we are using the JBoss Weld, which is the CDI reference implementation
and built into JBoss AS. It can be easily added to a servlet container using this maven fragment:

<dependency>
	<groupId>org.jboss.weld</groupId>
	<artifactId>weld-core</artifactId>
	<version>2.2.3.Final</version>
</dependency>
<dependency>
	<groupId>org.jboss.weld.servlet</groupId>
	<artifactId>weld-servlet-core</artifactId>
	<version>2.2.3.Final</version>
</dependency>

Weld comes with quite a few dependencies, so you might want to check them and for example exclude JSF or Guava.

Adding the dependency alone will not be sufficient. While CDI initializes on servlet container startup, it does not do anything for us yet. We will take care of that later.

CDI and Vaadin

Next we want to add CDI Support to Vaadin. What does that actually mean? After adding Weld, we could use standard CDI annotations already.
Vaadin has the concept of a UI. A user can have multiple UIs in her browser session. This means that @SessionScoped would not work, because then there could be only one UI per session, or these UIs would influence each other unexpectedly.
To solve that problem, Vaadin offers the vaadin-cdi plugin which is currently in alpha state.
It ships the @UIScoped annotation which will make sure each UI can have its own set of components and other injected @UIScoped dependencies.

CDI and MVP in Vaadin

To ease development of MVP style applications the cdi-mvp plugin can be used. It comes with base classes for views and presenters, as well as event support. Like vaadin-cdi, the cdi-mvp plugin is right now in alpha stage and unfortunately neither does see active development.
However they only consist of good ideas and little code. For our project we have combined the codebase of both and removed stuff we did not need.
We also needed to fix the BeanManager lookup which in Tomcat needs to look like this:

InitialContext initialContext = new InitialContext();
beanManager = (BeanManager) initialContext.lookup("java:comp/env/BeanManager");

Making Weld Active

When you would run the application now, you would get a message like “No active context for session scope found”. As said above, Weld has started its stuff but is not actually doing anything with servlet requests. The Weld documentation tells you that you need to add a servlet listener which will work as long as you do not use any push functionality.
But if you use push, do not use the servlet listener! It assumes that a request starts and ends, like it did in the olden days.
With push this is no longer true and you can get the strangest of errors from NullPointerExceptions to some users data showing up in another session!
Luckily Vaadin allows us to hook into code which is guaranteed to be invoked before and after any Vaadin activity, either sync or async.

Hooking into VaadinServletService

You most likely already have extended VaadinServlet to do custom initialisation. To activate Weld we need to override createServletService().

protected VaadinServletService createServletService(DeploymentConfiguration deploymentConfiguration)
    throws ServiceException {
  return new WeldVaadinServletService(this, deploymentConfiguration);
}

The code of WeldVaadinServletService is trivial. It activates and deactivates the WeldContexts on request start and request end.

public final class WeldVaadinServletService extends VaadinServletService {
 
  public WeldVaadinServletService(VaadinServlet servlet, DeploymentConfiguration deploymentConfiguration)
      throws ServiceException {
    super(servlet, deploymentConfiguration);
    init();
  }
 
  @Override
  public void requestStart(VaadinRequest request, VaadinResponse response) {
    VaadinWeldListener.initWeldForRequest(request);
    super.requestStart(request, response);
  }
 
  @Override
  public void requestEnd(VaadinRequest request, VaadinResponse response, VaadinSession session) {
    try {
      super.requestEnd(request, response, session);
    } finally {
      VaadinWeldListener.clearWeldForRequest(request);
    }
  }
}

Now you need to be strong. VaadinWeldListener contains a few hacks to activate the Weld contexts.

import static org.jboss.weld.environment.servlet.Listener.LISTENER_USED_ATTRIBUTE_NAME;
import static org.jboss.weld.servlet.ConversationFilter.CONVERSATION_FILTER_REGISTERED;
 
public class VaadinWeldListener implements ServletContextListener {
 
  private static WeldInitialListener WELD_LISTENER;
  private static ServletContext CTX;
 
  @Override
  public void contextInitialized(ServletContextEvent sce) {
    CTX = sce.getServletContext();
    // Do not use conversation filter. So claim its registered.
    CTX.setAttribute(CONVERSATION_FILTER_REGISTERED, Boolean.TRUE);
    // Claim that a listener is existing and start registering it ourselves.
    CTX.setAttribute(LISTENER_USED_ATTRIBUTE_NAME, Boolean.TRUE);
 
    WELD_LISTENER = new WeldInitialListener();
    WELD_LISTENER.contextInitialized(sce);
  }
 
  public static void initWeldForRequest(VaadinRequest vaadinRequest) {
    WELD_LISTENER.requestInitialized(new ServletRequestEvent(CTX, (VaadinServletRequest) vaadinRequest));
  }
 
  public static void clearWeldForRequest(VaadinRequest vaadinRequest) {
    WELD_LISTENER.requestDestroyed(new ServletRequestEvent(CTX, (VaadinServletRequest) vaadinRequest));
  }
 
  @Override
  public void contextDestroyed(ServletContextEvent sce) {
    WELD_LISTENER.contextDestroyed(sce);
  }
 
}

This does similar stuff to what the weld listener would have done. It registers a single instance of itself and makes sure that requestInitialized() and requestDestroyed() are invoked on a WeldInitialListener. It also sets attributes to prevent other parts of Weld doing that initialisation.
The main difference is that this code will now be invoked before and after each Vaadin activity, while the Weld listener would only listen to requestInitialized(ServletRequestEvent sre) and requestDestroyed(ServletRequestEvent sre) from ServletListener, which is however not guaranteed to be invoked for push. Also Weld does some assumption that both methods are invoked on the same worker thread. With push that is also not guaranteed anymore. Non blocking IO allows connections to suspend and be picked up by different worker threads later on.

Vaadin, Push and CDI live happily together ever after

Developing a Vaadin application with CDI is much easier than without.
Using the container built in CDI or Weld and the plugins however is not trivial and requires understanding about the request lifecycles and scopes. Unfortunately, even one year after the release of official support of push in Vaadin, there are still many problems to be found. If you decide to use push for more than trivial cases, you will end up debugging a lot.

Comment

Your email address will not be published. Required fields are marked *