2010

Right on the tail of the first alpha, Seam Servlet 3.0.0.Alpha2 has been released. Why the quick turnaround? Because we wanted to put two features in your hands so you can take control of that request.

  1. The ServletRequestContext lifecycle object
  2. Initial Seam Catch integration

The first feature let's you control the beginning of the request more easily. The second allows you to handle the tail of the request if bad $#*! happens.

If filters were events

While examining the Servlet API to determine what enhancements to make, I realized that it offers only a hodgepodge of lifecycle events. You get an event when the request is initialized, but no matching event for the response. You have to resort to using a servlet filter to access the response at the beginning of the request. But then you have to worry about whether you are handling an HTTP request and about managing the filter chain.

In the first alpha, I added a synthetic event for when the response is initialized and destroyed using a built-in filter. You can listen for this event using a CDI observer:

void onResponseInitialized(@Observes @Initialized ServletResponse response) {
   response.addHeader("Cache-Control", "no-cache");
}

You can even inject the request object into the observer method so that you can access both the request and the response:

void setEncoding(@Observes @Initialized ServletResponse res, ServletRequest req)
      throws Exception {
   req.setCharacterEncoding("UTF-8");
   res.setCharacterEncoding("UTF-8");
}

While this is a start, I thought of a few enhancements that would really make this ring:

  1. Group the request and response in a type together
  2. Short-circuit the request if the response is committed by a listener
  3. Allow the listener to observe a specific servlet path

Wrapping the request and response together was a no-brainer. One parameter is better than two. So I created the ServletRequestContext to provide access to the following implicit objects:

  • ServletRequest
  • ServletResponse
  • ServletContext

I also created its HTTP-counterpart, HttpServletRequestContext, which provides access to these objects:

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • ServletContext

These two objects can also be injected into any managed bean during the request.

We can now simplify the character encoding listener from the listing above:

void setEncoding(@Observes @Initialized ServletRequestContext ctx)
      throws Exception {
   ctx.getRequest().setCharacterEncoding("UTF-8");
   ctx.getResponse().setCharacterEncoding("UTF-8");
}

So far, we've only been considering listeners that contribute to the response. But what if you want to commit the response, such as send an HTTP error response or redirect to another page? With Seam Servlet, you can!

Here's a basic example showing a welcome page filter that redirects to a start page if the root URL is requested.

void redirectToStartPage(@Observes @Initialized HttpServletRequestContext ctx)
      throws Exception {
   String servletPath = ctx.getRequest().getServletPath();
   if (servletPath == null || servletPath.length() == 0 || servletPath.equals("/"))
   {
      String startPage = ctx.getResponse().encodeRedirectURL(
      ctx.getContextPath() + "/start.jsf");
      ctx.getResponse().sendRedirect(startPage);
   }
}

You also now have the option of associating the listener with a specific servlet path, in this case the context root, using the @Path qualifier:

void redirectToStartPage(@Observes @Path("") @Initialized HttpServletRequestContext ctx)
      throws Exception {
   String startPage = ctx.getResponse().encodeRedirectURL(ctx.getContextPath() + "/start.jsf");
   ctx.getResponse().sendRedirect(startPage);
}

You never have to write a Servlet listener, Servlet or Filter again!

When bad $#*! happens

Exceptions are a fact of life. As developers, we need to be prepared to deal with them in the most graceful manner possible. The Servlet API only gives you limited options for doing so:

  • send an HTTP status code
  • forward to an error page (servlet path)

To squeeze you into a tighter corner, you have to configure these exception mappings in web.xml. It just doesn't make this task easy. We have something that does.

Seam Catch provides a simple, yet robust foundation for modules and/or applications to establish a customized exception handling process. By employing a delegation model, Catch allow exceptions to be addressed in a centralized, extensible and uniform manner.

That sounds good, so how do you make use of it? That's where the Catch integration in Seam Servlet comes in.

When the request goes south (i.e., an unhandled exception occurs), and both Seam Servlet and Seam Catch are on the classpath, the exception will be caught and forwarded to your exception handler methods. How you deal with the exception from there is up to you.

Here's how you send an HTTP error response with a friendly message for any unhandled exception that occurs:

@HandlesExceptions
public class ExceptionHandlers {
   void handleAll(@Handles @WebRequest
         CaughtException<Throwable> caught, HttpServletResponse response) {
      response.sendError(500, "Sorry things didn't work out the way you expected."); 
   }
}

The @WebRequest qualifier distinguishes this exception handler from one that might apply to a JAX-RS request. (It will be optional in a future release)

Let's add another handler that sends a 404 response when the application-specific AccountNotFoundException is thrown.

void handleAccountNotFound(@Handles @WebRequest
      CaughtException<AccountNotFoundException> caught, HttpServletResponse response) {
   response.sendError(404, "Account not found: " + caught.getException().getAccountId()); 
}

Since you have access to the response object, you can send any sort of response you want to the browser. The next release of Seam Servlet will provide handler annotations so you can configure these two responses above declaratively. Stay tuned!

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.Alpha2</version>
</dependency>

If you're not deploying to JBoss AS, you 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>

There's plenty more features to explore in Seam Servlet, and more to come, so check it out!

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

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: