SeamFramework.orgCommunity Documentation

Seam Servlet Module

Reference Guide

3.0.0-SNAPSHOT


Introduction
1. Installation
1.1. Maven dependency configuration
1.2. Pre-Servlet 3.0 configuration
2. Servlet event propagation
2.1. Servlet context lifecycle listener
2.2. Application initialization
2.3. Servlet request lifecycle listener
2.4. Servlet response lifecycle listener
2.5. Servlet request context lifecycle listener
2.6. Session lifecycle listener
2.7. Session activation listener
3. Injectable Servlet objects and request state
3.1. @Inject @RequestParam
3.2. @Inject @HeaderParam
3.3. @Inject ServletContext
3.4. @Inject ServletRequest / HttpServletRequest
3.5. @Inject ServletResponse / HttpServletResponse
3.6. @Inject HttpSession
3.7. @Inject HttpSessionStatus
3.8. @Inject @ContextPath
3.9. @Inject List<Cookie>
3.10. @Inject @CookieParam
3.11. @Inject @ServerInfo
3.12. @Inject @Principal
4. Exception handling: Seam Catch integration
4.1. Background
4.2. Defining a exception handler for a web request
5. Retrieving the BeanManager from the servlet context

The goal of the Seam Servlet module is to provide portable enhancements to the Servlet API. Features include producers for implicit Servlet objects and HTTP request state, propagating Servlet events to the CDI event bus, forwarding uncaught exceptions to the Seam Catch handler chain and binding the BeanManager to a Servlet context attribute for convenient access.

To use the Seam Servlet module, you need to put the API and implementation JARs on the classpath of your web application. Most of the features of Seam Servlet are enabled automatically when it's added to the classpath. Some extra configuration, covered below, is required if you are not using a Servlet 3-compliant container.

If you are using Maven as your build tool, you can add the following single dependency to your pom.xml file to include Seam Servlet:


<dependency>
   <groupId>org.jboss.seam.servlet</groupId>
   <artifactId>seam-servlet-impl</artifactId>
   <version>${seam.servlet.version}</version>
</dependency>

Tip

Substitute the expression ${seam.servlet.version} with the most recent or appropriate version of Seam Servlet. Alternatively, you can create a Maven user-defined property to satisfy this substitution so you can centrally manage the version.

Alternatively, you can use the API at compile time and only include the implementation at runtime. This protects you from inadvertantly depending on an implementation class.


<dependency>
   <groupId>org.jboss.seam.servlet</groupId>
   <artifactId>seam-servlet-api</artifactId>
   <version>${seam.servlet.version}</version>
   <scope>compile</scope>
</dependency>

<dependency>
   <groupId>org.jboss.seam.servlet</groupId>
   <artifactId>seam-servlet-impl</artifactId>
   <version>${seam.servlet.version}</version>
   <scope>runtime</scope>
</dependency>

If you are deploying to a platform other than JBoss AS, you also need to add the JBoss Logging implementation (a portable logging abstraction).


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

In a Servlet 3.0 or Java EE 6 environment, your configuration is now complete!

You're now ready to dive into the Servlet enhancements provided for you by the Seam Servlet module!

By including the Seam 3 Servlet module in your web application (and performing the necessary listener configuration for pre-Servlet 3.0 environments) you will also have the servlet lifecycle events propagated to the CDI event bridge so you can observe them in your beans. Seam Servlet also fires some additional lifecycle events not offered by the Servlet API, such as when the response is initialized and destroyed.

These events correspond to the javax.servlet.ServletRequestListener interface. The event object fired is a javax.servlet.ServletRequest (since that's the only relevant information in the javax.servlet.ServletRequestEvent object. There are two qualifiers available that can be used for selecting the lifecycle phase of the request (initialize or destroy) and one to filter the observer based on the servlet path.

QualifierDescription
@InitializedQualifies the initialization event
@DestroyedQualifies the destruction event
@PathQualifies the servlet path of the request (no leading slash)

If you want to listen to both lifecycle events, leave out the qualifiers.

public void observeRequest(@Observes ServletRequest request) {

   // Do something with the servlet "request" object
}

If you are interested in only a particular one, use a qualifer

public void observeRequestInitialized(@Observes @Initialized ServletRequest request) {

   // Do something with the servlet "request" object upon initialization
}

You can also listen specifically for a javax.servlet.http.HttpServletRequest simply by changing the expected event type.

public void observeRequestInitialized(@Observes @Initialized HttpServletRequest request) {

   // Do something with the HTTP servlet "request" object upon initialization
}

You can associate an observer with a particular servlet request path (exact match, dropping the leading slash).

public void observeRequestInitialized(@Observes @Initialized @Path("offer") HttpServletRequest request) {

   // Do something with the HTTP servlet "request" object upon initialization
   // only when servlet path /offer is requested
}

The name of the observer method is insignificant.

The Servlet API does not provide a listener for accessing the lifecycle of a response. Therefore, Seam Servlet simulates a response lifecycle listener using CDI events. These events parallel those provided by the javax.servlet.ServletRequestListener inteface. The event object fired is a javax.servlet.ServletResponse. There are two qualifiers available that can be used for selecting the lifecycle phase of the response (initialize or destroy) and one to filter the observer based on the servlet path.

QualifierDescription
@InitializedQualifies the initialization event
@DestroyedQualifies the destruction event
@PathQualifies the servlet path of the request (no leading slash)

If you want to listen to both lifecycle events, leave out the qualifiers.

public void observeResponse(@Observes ServletResponse response) {

   // Do something with the servlet "response" object
}

If you are interested in only a particular one, use a qualifer

public void observeResponseInitialized(@Observes @Initialized ServletResponse response) {

   // Do something with the servlet "response" object upon initialization
}

You can also listen specifically for a javax.servlet.http.HttpServletResponse simply by changing the expected event type.

public void observeResponseInitialized(@Observes @Initialized HttpServletResponse response) {

   // Do something with the HTTP servlet "response" object upon initialization
}

If you need access to the ServletRequest and/or the ServletContext objects at the same time, you can simply add them as parameters to the observer methods. For instance, let's assume you want to manually set the character encoding of the request and response.

public void setupEncoding(@Observes @Initialized ServletResponse res, ServletRequest req) throws Exception {

   if (this.override || req.getCharacterEncoding() == null) {
      req.setCharacterEncoding(encoding);
      if (override) {
         res.setCharacterEncoding(encoding);
      }
   }
}

The name of the observer method is insignificant.

Rather than having to observe the request and response as separate events, or include the request object as an parameter on a response observer, it would be convenient to be able to observe them as a pair. That's why Seam Servlet fires an synthetic lifecycle event for the wrapper type ServletRequestContext. The ServletRequestContext holds the ServletRequest and the ServletResponse objects, and also provides access to the ServletContext. There are two qualifiers available that can be used for selecting the lifecycle phase of the request context (initialize or destroy) and one to filter the observer based on the servlet path.

QualifierDescription
@InitializedQualifies the initialization event
@DestroyedQualifies the destruction event
@PathQualifies the servlet path of the request (no leading slash)

Let's revisit the character encoding observer and examine how it can be simplified by this event:

public void setupEncoding(@Observes @Initialized ServletRequestContext ctx) throws Exception {

   if (this.override || ctx.getRequest().getCharacterEncoding() == null) {
      ctx.getRequest().setCharacterEncoding(encoding);
      if (override) {
         ctx.getResponse().setCharacterEncoding(encoding);
      }
   }
}

You can also observe the HttpServletRequestContext to be notified only on HTTP requests.

Since observers that have access to the response can commit it, an HttpServletRequestContext observer that receives the initialized event can effectively work as a filter or even a Servlet. Let's consider a primitive welcome page filter that redirects visitors to the start page:

public void redirectToStartPage(@Observes @Path("") @Initialized HttpServletRequestContext ctx)

      throws Exception {
   String startPage = ctx.getResponse().encodeRedirectURL(ctx.getContextPath() + "/start.jsf");
   ctx.getResponse().sendRedirect(startPage);
}

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

Seam Servlet provides producers that expose a wide-range of information available in a Servlet environment (e.g., implicit objects such as ServletContext and HttpSession and state such as HTTP request parameters) as beans. You access this information by injecting the beans produced. This chapter documents the Servlet objects and request state that Seam Servlet exposes and how to inject them.

The @RequestParam qualifier allows you to inject an HTTP request parameter (i.e., URI query string or URL form encoded parameter).

Assume a request URL of /book.jsp?id=1.

@Inject @RequestParam("id")

private String bookId;

The value of the specified request parameter is retrieved using the method ServletRequest.getParameter(String). It is then produced as a dependent-scoped bean of type String qualified @RequestParam.

The name of the request parameter to lookup is either the value of the @RequestParam annotation or, if the annotation value is empty, the name of the injection point (e.g., the field name).

Here's the example from above modified so that the request parameter name is implied from the field name:

@Inject @RequestParam

private String id;

If the request parameter is not present, and the injection point is annotated with @DefaultValue, the value of the @DefaultValue annotation is returned instead.

Here's an example that provides a fall-back value:

@Inject @RequestParam @DefaultValue("25")

private String pageSize;

If the request parameter is not present, and the @DefaultValue annotation is not present, a null value is injected.

Seam Catch provides a simple, yet robust foundation for modules and/or applications to establish a customized exception handling process. Seam Servlet ties into the exception handling model by forwarding all unhandled Servlet exceptions to Catch so that they can be handled in a centralized, extensible and uniform manner.

You can define an exception handler for a web request using the normal syntax of a Catch exception handler. Let's catch any exception that bubbles to the top and respond with a 500 error.

@HandlesExceptions

public class ExceptionHandlers {
   void handleAll(@Handles CaughtException<Throwable> caught, HttpServletResponse response) {
      response.sendError(500, "You've been caught by Catch!"); 
   }
}

That's all there is to it! If you only want this handler to be used for exceptions raised by a web request (excluding web service requests like JAX-RS), then you can add the @WebRequest qualifier to the handler:

@HandlesExceptions

public class ExceptionHandlers {
   void handleAll(@Handles @WebRequest
         CaughtException<Throwable> caught, HttpServletResponse response) {
      response.sendError(500, "You've been caught by Catch!"); 
   }
}

Let's consider another example. When the custom AccountNotFound exception is thrown, we'll send a 404 response using this handler.

void handleAccountNotFound(@Handles @WebRequest

      CaughtException<AccountNotFound> caught, HttpServletResponse response) {
   response.sendError(404, "Account not found: " + caught.getException().getAccountId()); 
}

In a future release, Seam Servlet will include annotations that can be used to configure these responses declaratively.

Typically, the BeanManager is obtained using some form of injection. However, there are scenarios where the code being executed is outside of a managed bean environment and you need a way in. In these cases, it's necessary to lookup the BeanManager from a well-known location.

The standard mechanism for locating the BeanManager from outside a managed bean environment, as defined by the JSR-299 specification, is to look it up in JNDI. However, JNDI isn't the most convenient technology to depend on when you consider all popular deployment environments (think Tomcat and Jetty).

As a simpler alternative, Seam Servlet binds the BeanManager to the following servlet context attribute (whose name is equivalent to the fully-qualified class name of the BeanManager interface:

javax.enterprise.inject.spi.BeanManager

Seam Servlet also includes a provider that retrieves the BeanManager from this location. Anytime the Seam Servlet module needs a reference to the BeanManager, it uses this lookup mechanism to ensure that the module works consistently across deployment environments, especially in Servlet containers.

You can retrieve the BeanManager in the same way. If you want to hide the lookup, you can extend the BeanManagerAware class and retrieve the BeanManager from the the method getBeanManager(), as shown here:



public class NonManagedClass extends BeanManagerAware {
   public void fireEvent() {
      getBeanManager().fireEvent("Send me to a managed bean");
   }
}
   

Alternatively, you can retrieve the BeanManager from the static method getBeanManager() on the BeanManagerAccessor class, as shown here:



public class NonManagedClass {
   public void fireEvent() {
      BeanManagerAccessor.getBeanManager().fireEvent("Send me to a managed bean");
   }
}
   

Under the covers, these classes look for the BeanManager in the servlet context attribute covered in this section, amongst other available strategies. Refer to the BeanManager provider chapter of the Weld Extensions reference guide for information on how to leverage the servlet context attribute provider to access the BeanManager from outside the CDI environment.