Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Follow the Chain of Responsibility

Run through server-side and client-side CoR implementations

  • Print
  • Feedback

Page 3 of 4

Servlet filters are a powerful addition to J2EE. Also, from a design patterns standpoint, they provide an interesting twist: If you want to modify the request or the response, you use the Decorator pattern in addition to CoR. Figure 3 shows how servlet filters work.

Figure 3. Servlet filters at runtime

A simple servlet filter

You must do three things to filter a servlet:

  • Implement a servlet
  • Implement a filter
  • Associate the filter and the servlet


Examples 1-3 perform all three steps in succession:

Example 1. A servlet

import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
public class FilteredServlet extends HttpServlet {
   public void doGet(HttpServletRequest request, HttpServletResponse response)
                         throws ServletException, java.io.IOException {
      PrintWriter out = response.getWriter();
      out.println("Filtered Servlet invoked");
   }
}


Example 2. A filter

import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
public class AuditFilter implements Filter {
   private ServletContext app = null;
   public void init(FilterConfig config) { 
      app = config.getServletContext();
   }
   public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws java.io.IOException,
                                               javax.servlet.ServletException {
      app.log(((HttpServletRequest)request).getServletPath());
      chain.doFilter(request, response);
   }
   public void destroy() { }
}


Example 3. The deployment descriptor

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2.3.dtd">
<web-app>
   <filter>
      <filter-name>auditFilter</filter-name>
      <filter-class>AuditFilter</filter-class>
   </filter>
   <filter-mapping>
      <filter-name>auditFilter</filter-name>
      <servlet-name>/filteredServlet</servlet-name>
   </filter-mapping>
     <!-- Servlet Configuration -->
     <servlet>
       <servlet-name>filteredServlet</servlet-name>
       <servlet-class>FilteredServlet</servlet-class>
     </servlet>
     <servlet-mapping>
       <servlet-name>filteredServlet</servlet-name>
       <url-pattern>/filteredServlet</url-pattern>
     </servlet-mapping>
   ...
</web-app>


If you access the servlet with the URL /filteredServlet, the auditFilter gets a crack at the request before the servlet. AuditFilter.doFilter writes to the servlet container log file and calls chain.doFilter() to forward the request. Servlet filters are not required to call chain.doFilter(); if they don't, the request is not forwarded. I can add more filters, which would be invoked in the order they are declared in the preceding XML file.

Now that you've seen a simple filter, let's look at another filter that modifies the HTTP response.

Filter the response with the Decorator pattern

Unlike the preceding filter, some servlet filters need to modify the HTTP request or response. Interestingly enough, that task involves the Decorator pattern. I discussed the Decorator pattern in two previous Java Design Patterns articles: "Amaze Your Developer Friends with Design Patterns" and "Decorate Your Java Code."

Example 4 lists a filter that performs a simple search and replace in the body of the response. That filter decorates the servlet response and passes the decorator to the servlet. When the servlet finishes writing to the decorated response, the filter performs a search and replace within the response's content.

Example 4. A search and replace filter

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SearchAndReplaceFilter implements Filter {
   private FilterConfig config;
   public void init(FilterConfig config) { this.config = config; }
   public FilterConfig getFilterConfig() { return config; }
   public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws java.io.IOException,
                                               javax.servlet.ServletException {
      StringWrapper wrapper = new StringWrapper((HttpServletResponse)response);
      chain.doFilter(request, wrapper);
      String responseString = wrapper.toString();
      String search = config.getInitParameter("search");
      String replace = config.getInitParameter("replace");
      if(search == null || replace == null)
         return; // Parameters not set properly
      int index = responseString.indexOf(search);
      if(index != -1) {
         String beforeReplace = responseString.substring(0, index);
         String afterReplace=responseString.substring(index + search.length());
         response.getWriter().print(beforeReplace + replace + afterReplace);
      }
   }
   public void destroy() {
      config = null;
   }
}


The preceding filter looks for filter init parameters named search and replace; if they are defined, the filter replaces the first occurrence of the search parameter value with the replace parameter value.

SearchAndReplaceFilter.doFilter() wraps (or decorates) the response object with a wrapper (decorator) that stands in for the response. When SearchAndReplaceFilter.doFilter() calls chain.doFilter() to forward the request, it passes the wrapper instead of the original response. The request is forwarded to the servlet, which generates the response.

When chain.doFilter() returns, the servlet is done with the request, so I go to work. First, I check for the search and replace filter parameters; if present, I obtain the string associated with the response wrapper, which is the response content. Then I make the substitution and print it back to the response.

Example 5 lists the StringWrapper class.

Example 5. A decorator

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class StringWrapper extends HttpServletResponseWrapper {
   StringWriter writer = new StringWriter();
   public StringWrapper(HttpServletResponse response) { super(response); }
   public PrintWriter getWriter() { return new PrintWriter(writer); }
   public String       toString() { return writer.toString(); }
}


StringWrapper, which decorates the HTTP response in Example 4, is an extension of HttpServletResponseWrapper, which spares us the drudgery of creating a decorator base class for decorating HTTP responses. HttpServletResponseWrapper ultimately implements the ServletResponse interface, so instances of HttpServletResponseWrapper can be passed to any method expecting a ServletResponse object. That's why SearchAndReplaceFilter.doFilter() can call chain.doFilter(request, wrapper) instead of chain.doFilter(request, response).

Now that we have a filter and a response wrapper, let's associate the filter with a URL pattern and specify search and replace patterns:

Example 6. A deployment descriptor

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2.3.dtd">
<web-app>
   ...
   <filter>
      <filter-name>searchAndReplaceFilter</filter-name>
      <filter-class>SearchAndReplaceFilter</filter-class>
      <init-param>
         <param-name>search</param-name>
         <param-value>Blue Road Inc.</param-value>
      </init-param>
      
      <init-param>
         <param-name>replace</param-name>
         <param-value>Red Rocks Inc.</param-value>
      </init-param>
   </filter>
   <filter-mapping>
      <filter-name>searchAndReplaceFilter</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
   ...
</web-app>


I've now wired the search and replace filter to all requests by associating it with the URL pattern /* and specified that Red Rocks Inc. will replace the first occurrence of Blue Road Inc.. Let's try it on this JavaServer Pages (JSP) page:

Example 7. A JSP page

Welcome to Blue Road Inc.


Figure 4 shows the preceding JSP page's output. Notice that Red Rocks Inc. has replaced Blue Road Inc..

Figure 4. Using a search and replace filter. Click on thumbnail to view full-size image.

Of course, my search and replace filter serves little practical use other than demonstrating how filters can modify the response with a wrapper. However, useful freely available servlet filters are easy to find. For example, Tomcat 4.1.X comes with the following filters: a compression filter that zips responses larger than a threshold (that you can set as a filter parameter); an HTML filter; and a filter that dumps information about a request. You can find those filters under Tomcat's examples directory.

Servlet filters represent a popular CoR pattern variation where multiple objects in the chain may handle a request. Let's wrap up the CoR pattern with a brief look at a classic CoR implementation that was deprecated.

The AWT event model

The AWT originally used the CoR pattern for event handling. This is how it worked:

import java.applet.Applet;
import java.awt.*;
public class MouseSensor extends Frame {
   public static void main(String[] args) {
      MouseSensor ms = new MouseSensor();
      ms.setBounds(10,10,200,200);
      ms.show();
   }
   public MouseSensor() {
      setLayout(new BorderLayout());
      add(new MouseSensorCanvas(), "Center");
   }
}
class MouseSensorCanvas extends Canvas {
   public boolean mouseUp(Event event, int x, int y) {
      System.out.println("mouse up");
      return true; // Event has been handled. Do not propagate to container.
   }
   public boolean mouseDown(Event event, int x, int y) {
      System.out.println("mouse down");
      return true; // Event has been handled. Do not propagate to container.
   }
}


The preceding application creates a canvas and adds it to the application. That canvas handles mouse up and down events by overriding mouseUp() and mouseDown(), respectively. Notice those methods return a boolean value: true signifies that the event has been handled, and therefore should not be propagated to the component's container; false means the event was not fully handled and should be propagated. Events bubble up the component hierarchy until a component handles it; or the event is ignored if no component is interested. This is a classic Chain of Responsibility implementation.

  • Print
  • Feedback

Resources