Skip navigation

Dan's Blog

December 2, 2010 Previous day Next day

Never write a Servlet listener again! After all, who wants to implement container interfaces? With the Seam Servlet module, you no longer have to!

Servlet event bridge

The first alpha release (3.0.0.Alpha1) of the Seam Servlet module brings deeper integration of CDI and the Servlet API. The integration kicks off by channeling Servlet lifecycle events to the CDI event bus. That means you can use simple CDI observer methods to respond to Servlet lifecycle events, such as the request initialized event. You listen for the exact event you are interested in with no additional configuration necessary.

Here's a basic example that prints the User-Agent that issued the request:

public class UserAgentPrinter
{
   public void printUserAgent(@Observes @Initialized HttpServletRequest request)
   {
      System.out.println("Request issued by " + request.getHeader("User-Agent"));
   }
}

Perhaps that doesn't impress you much (if so, great). There is a golden egg hidden in this bridge.

Application initializers

Put away those startup singletons you're so excited about. You don't need them. The Servlet context initialized event is much better suited for application initialization routines. Since the observer is a CDI bean, you can inject anything you might need to get setup. Even better, the bean can be configured to be released as soon as the setup routine is complete by making it dependent-scoped (the default). In contrast, when you use a startup singleton, it hangs around for the lifetime of the application.

Here's the classic case of inserting seed data into the database when the application is starting up:

public class SeedDataImporter
{
   @PersistenceContext
   private EntityManager em;
    
   @Inject
   private UserTransaction utx;
   
   public void importData(@Observes @Initialized ServletContext ctx)
         throws Exception
   {
      utx.begin();
      em.joinTransaction();
      em.persist(new Product(1, "Black Hole", 50d));
      ...
      utx.commit();
   }
}

Or better yet, just make it an EJB:

@Stateless
public class SeedDataImporter
{
   @PersistenceContext
   private EntityManager em;
    
   @Inject
   private UserTransaction utx;
   
   public void importData(@Observes @Initialized ServletContext ctx)
         throws Exception
   {
      em.persist(new Product(1, "Black Hole", 50d));
      ...
   }
}

If you don't want to tie your code to the Servlet API, you have the option of observing WebApplication, a value object provided by Seam Servlet:

public void importData(@Observes @Initialized WebApplication webapp)
      throws Exception { ... }

Injection galore

Seam Servlet also exposes the implicit Servlet objects, such as ServletContext, ServletRequest (and its HTTP counterpart) and HttpSession, as appropriately-scoped, injectable resources.

Here's an example of how you inject the HttpServletRequest:

@Inject
private HttpServletRequest request;

There's one important implicit object that's been left out, the response. Since the Servlet API does not raise lifecycle events for the ServletResponse (and its HTTP counterpart), Seam Servlet steps up and emulates the lifecycle events for this implicit object using a Servlet filter. That gives you access to the response object in a lifecycle observer! Here's an example that sets the character encoding on the request and response:

public class CharacterEncodingSetup
{
   public void setup(@Observes @Initialized ServletResponse req, ServletRequest res)
         throws Exception
   {
      request.setCharacterEncoding("UTF-8");
      response.setCharacterEncoding("UTF-8");
   }
}

You can also access the response through injection, as shown here:

@Inject
private HttpServletResponse response;

In addition to implicit objects, Seam Servlet also allows you to inject contextual HTTP state; in simpler terms, request parameters, headers and cookies. As you would expect, you can inject these values as strings.

// retrieves the value "chocolate" from query string ?flavor=chocolate
@Inject @RequestParam @DefaultValue("vanilla")
private String flavor;

Leveraging a feature from Weld Extensions (Seam Solder) known as a "narrowing bean", Seam Servlet can also inspect the type of the injection point and convert the value to that type before performing the injection.

// retrieves the long value 9 from query string ?id=9
@Inject @RequestParam("id")
private Long bookId;

Finally, Seam Servlet addresses a bit of a pain point in CDI: accessing the BeanManager.

Easy BeanManager access

When the code you are writing is inside of a bean (or other resource) managed by CDI, you can access other beans or the BeanManager using some form of injection. There are scenarios where you are starting outside of this managed environment and you need a way in. According to the JSR-299 specification, that way in is through a JNDI lookup. Did that make you cringe?

The fact is, JNDI isn't universal or consistent across all popular deployment environments (think Tomcat and Jetty). To level the playing field, Seam Servlet binds the BeanManager to the Servlet context attribute javax.enterprise.inject.spi.BeanManager (the fully-qualified class name of BeanManager). Now you have two ways to find the BeanManager, the later being ideal for Servlet applications.

If you want to hide the lookup completely, you can use the BeanManagerAware super class or a static method on the BeanManagerAccessor class to obtain a reference to the BeanManager. Under the covers it will consult this Servlet context attribute, amongst other lookup strategies.

public class NonManagedClass extends BeanManagerAware
{
   public void fireEvent()
   {
      getBeanManager().fireEvent("Hello!");
   }
}

Get started!

As with all Seam 3 modules, the Seam Servlet module is published to the JBoss Community repository. Here's the dependency declaration you need to add to your POM to include this module in your project:

<dependency>
   <groupId>org.jboss.seam.servlet</groupId>
   <artifactId>seam-servlet-impl</artifactId>
   <version>3.0.0.Alpha1</version>
</dependency>

If you are not deploying to JBoss AS, you will also need JBoss Logging (a portable logging abstraction):

<dependency>
   <groupId>org.jboss.logging</groupId>
   <artifactId>jboss-logging</artifactId>
   <version>3.0.0.Beta4</version>
   <scope>provided</scope>
</dependency>

At the moment you cannot use Seam Servlet on GlassFish because several of the extension points used by Weld Extensions (Seam Solder) are not functioning correctly on GlassFish. This is an issue that affects most Seam modules at the moment, though you can expect a resolution soon. See GLASSFISH-14808 and related issues.

There's plenty more features to explore in Seam Servlet. The next major addition, coming in Alpha 2, is CDI-based exception handling through integration with the Seam Catch module. Check out the Seam Servlet module page or issue tracker for more details (links below).

[ Module Overview ] | [ Download Distribution ] | [ JIRA ] | [ Reference Guide ]

Filter Blog

By date:
By tag: