Core J2EE Patterns - Intercepting Filter

   

Context

The presentation-tier request handling mechanism receives many different types of requests, which require varied types of processing. Some requests are simply forwarded to the appropriate handler component, while other requests must be modified, audited, or uncompressed before being further processed.

Problem

Preprocessing and post-processing of a client Web request and response are required.

When a request enters a Web application, it often must pass several entrance tests prior to the main processing stage. For example,

  • Has the client been authenticated?

  • Does the client have a valid session?
  • Is the client's IP address from a trusted network?
  • Does the request path violate any constraints?
  • What encoding does the client use to send the data?
  • Do we support the browser type of the client?

Some of these checks are tests, resulting in a yes or no answer that determines whether processing will continue. Other checks manipulate the incoming data stream into a form suitable for processing.

The classic solution consists of a series of conditional checks, with any failed check aborting the request. Nested if/else statements are a standard strategy, but this solution leads to code fragility and a copy-and-paste style of programming, because the flow of the filtering and the action of the filters is compiled into the application.

The key to solving this problem in a flexible and unobtrusive manner is to have a simple mechanism for adding and removing processing components, in which each component completes a specific filtering action.

Forces

  • Common processing, such as checking the data-encoding scheme or logging information about each request, completes per request.

  • Centralization of common logic is desired.
  • Services should be easy to add or remove unobtrusively without affecting existing components, so that they can be used in a variety of combinations, such as
    • Logging and authentication
    • Debugging and transformation of output for a specific client
    • Uncompressing and converting encoding scheme of input

Solution

Create pluggable filters to process common services in a standard manner without requiring changes to core request processing code. The filters intercept incoming requests and outgoing responses, allowing preprocessing and post-processing. We are able to add and remove these filters unobtrusively, without requiring changes to our existing code.

We are able, in effect, to decorate our main processing with a variety of common services, such as security, logging, debugging, and so forth. These filters are components that are independent of the main application code, and they may be added or removed declaratively. For example, a deployment configuration file may be modified to set up a chain of filters. The same configuration file might include a mapping of specific URLs to this filter chain. When a client requests a resource that matches this configured URL mapping, the filters in the chain are each processed in order before the requested target resource is invoked.

Structure

Figure 7.1 represents the Intercepting Filter pattern.

Figure 7.1
Figure 7.1 Intercepting Filter pattern class diagram

Participants and Responsibilities

Figure 7.2 represents the Intercepting Filter pattern.

Figure 7.2
Figure 7.2 Intercepting Filter sequence diagram

FilterManager

The FilterManager manages filter processing. It creates the FilterChain with the appropriate filters, in the correct order, and initiates processing.

FilterChain

The FilterChain is an ordered collection of independent filters.

FilterOne, FilterTwo, FilterThree

These are the individual filters that are mapped to a target. The FilterChain coordinates their processing.

Target

The Target is the resource requested by the client.

Strategies

Custom Filter Strategy

Filter is implemented via a custom strategy defined by the developer. This is less flexible and less powerful than the preferred Standard Filter Strategy, which is presented in the next section and is only available in containers supporting the 2.3 servlet specification. The Custom Filter Strategy is less powerful because it cannot provide for the wrapping of request and response objects in a standard and portable way. Additionally, the request object cannot be modified, and some sort of buffering mechanism must be introduced if filters are to control the output stream. To implement the Custom Filter Strategy, the developer could use the Decorator pattern [GoF] to wrap filters around the core request processing logic. For example, there may be a debugging filter that wraps an authentication filter. Example 7.1 and Example 7.2 show how this mechanism might be created programmatically:

Example 7.1 Implementing a Filter - Debugging Filter

public class DebuggingFilter implements Processor {
  private Processor target;

  public DebuggingFilter(Processor myTarget) {
    target = myTarget;
  }

  public void execute(ServletRequest req, 
  ServletResponse res) throws IOException, 
    ServletException    {
    //Do some filter processing here, such as 
    // displaying request parameters
    target.execute(req, res);
  }
}


Example 7.2 Implementing a Filter - Core Processor

public class CoreProcessor implements Processor {
  private Processor target;
  public CoreProcessor()   {
    this(null);
  }

  public CoreProcessor(Processor myTarget)   {
    target = myTarget;
  }

  public void execute(ServletRequest req, 
      ServletResponse res) throws IOException, 
      ServletException   {
    //Do core processing here
  }
}

In the servlet controller, we delegate to a method called processRequest to handle incoming requests, as shown in Example 7.3.

Example 7.3 Handling Requests

public void processRequest(ServletRequest req, 
  ServletResponse res) 
  throws IOException, ServletException {
  Processor processors = new DebuggingFilter( 
    new AuthenticationFilter(new CoreProcessor()));
  processors.execute(req, res);

  //Then dispatch to next resource, which is probably 
  // the View to display
  dispatcher.dispatch(req, res);
}

For example purposes only, imagine that each processing component writes to standard output when it is executed. Example 7.4 shows the possible execution output.

Example 7.4 Messages Written to Standard Output

Debugging filter preprocessing completed...
Authentication filter processing completed...
Core processing completed...
Debugging filter post-processing completed...

A chain of processors is executed in order. Each processor, except for the last one in the chain, is considered a filter. The final processor component is where we encapsulate the core processing we want to complete for each request. Given this design, we will need to change the code in the CoreProcessor class, as well as in any filter classes, when we want to modify how we handle requests.

Figure 7.3 is a sequence diagram describing the flow of control when using the filter code of Example 7.1, Example 7.2, and Example 7.3.

Figure 7.3
Figure 7.3 Sequence diagram for Custom Filter Strategy, decorator implementation

Notice that when we use a decorator implementation, each filter invokes on the next filter directly, though using a generic interface. Alternatively, this strategy can be implemented using a FilterManager and FilterChain. In this case, these two components coordinate and manage filter processing and the individual filters do not communicate with one another directly. This design approximates that of a servlet 2.3-compliant implementation, though it is still a custom strategy. Example 7.5 is the listing of just such a FilterManager class that creates a FilterChain, which is shown in Example 7.6. The FilterChain adds filters to the chain in the appropriate order (for the sake of brevity, this is done in the FilterChain constructor, but would normally be done in place of the comment), processes the filters, and finally processes the target resource. Figure 7.4 is a sequence diagram for this code.

Figure 7.4
Figure 7.4 Sequence diagram for Custom Filter Strategy, nondecorator implementation

Example 7.5 FilterManager - Custom Filter Strategy

public class FilterManager {
  public void processFilter(Filter target, 
    javax.servlet.http.HttpServletRequest request, 
    javax.servlet.http.HttpServletResponse response) 
    throws javax.servlet.ServletException, 
      java.io.IOException       {                    
    FilterChain filterChain = new FilterChain();

    // The filter manager builds the filter chain here 
    // if necessary

    // Pipe request through Filter Chain
    filterChain.processFilter(request, response);

    //process target resource
    target.execute(request, response);
  }
}


Example 7.6 FilterChain - Custom Filter Strategy

public class FilterChain {
  // filter chain 
  private Vector myFilters = new Vector();

  // Creates new FilterChain 
  public FilterChain()  {
    // plug-in default filter services for example 
    // only. This would typically be done in the 
    // FilterManager, but is done here for example 
    // purposes
    addFilter(new DebugFilter());
    addFilter(new LoginFilter());
    addFilter(new AuditFilter());
  }

  public void processFilter( 
    javax.servlet.http.HttpServletRequest request,
    javax.servlet.http.HttpServletResponse response)
  throws javax.servlet.ServletException, 
    java.io.IOException         {
    Filter filter;

    // apply filters
    Iterator filters = myFilters.iterator();
    while (filters.hasNext())
    {
      filter = (Filter)filters.next();
      // pass request & response through various 
      // filters
      filter.execute(request, response);
    }
  }

  public void addFilter(Filter filter)  {
    myFilters.add(filter);
  }
}

This strategy does not allow us to create filters that are as flexible or as powerful as we would like. For one, filters are added and removed programmatically. While we could write a proprietary mechanism for handling adding and removing filters via a configuration file, we still would have no way of wrapping the request and response objects. Additionally, without a sophisticated buffering mechanism, this strategy does not provide flexible postprocessing.

The Standard Filter Strategy provides solutions to these issues, leveraging features of the 2.3 Servlet specification, which has provided a standard solution to the filter dilemma.

Note

As of this writing, the Servlet 2.3 specification is in final draft form.

Standard Filter Strategy

Filters are controlled declaratively using a deployment descriptor, as described in the servlet specification version 2.3, which, as of this writing, is in final draft form. The servlet 2.3 specification includes a standard mechanism for building filter chains and unobtrusively adding and removing filters from those chains. Filters are built around interfaces, and added or removed in a declarative manner by modifying the deployment descriptor for a Web application.

Our example for this strategy will be to create a filter that preprocesses requests of any encoding type such that each request may be handled similarly in our core request handling code. Why might this be necessary? HTML forms that include a file upload use a different encoding type than that of most forms. Thus, form data that accompanies the upload is not available via simple getParameter() invocations. So, we create two filters that preprocess requests, translating all encoding types into a single consistent format. The format we choose is to have all form data available as request attributes.

One filter handles the standard form encoding of type application/ x-www-form-urlencoded and the other handles the less common encoding type multipart/form-data, which is used for forms that include file uploads. The filters translate all form data into request attributes, so the core request handling mechanism can work with every request in the same manner, instead of with special casing for different encodings.

Example 7.8 shows a filter that translates requests using the common application form encoding scheme. Example 7.9 shows the filter that handles the translation of requests that use the multipart form encoding scheme. The code for these filters is based on the final draft of the servlet specification, version 2.3. A base filter is used as well, from which both of these filters inherit (see the section "Base Filter Strategy"). The base filter, shown in Example 7.7, provides default behavior for the standard filter callback methods.

Example 7.7 Base Filter - Standard Filter Strategy

public class BaseEncodeFilter implements 
      javax.servlet.Filter {
  private javax.servlet.FilterConfig myFilterConfig;

  public BaseEncodeFilter()     {  }

  public void doFilter(
    javax.servlet.ServletRequest servletRequest, 
    javax.servlet.ServletResponse servletResponse,
    javax.servlet.FilterChain filterChain) 
  throws java.io.IOException,
    javax.servlet.ServletException {
    filterChain.doFilter(servletRequest, 
        servletResponse);
  }

  public javax.servlet.FilterConfig getFilterConfig() 
  {
    return myFilterConfig; 
  }
    
  public void setFilterConfig(
    javax.servlet.FilterConfig filterConfig) {
      myFilterConfig = filterConfig;
  }
}


Example 7.8 StandardEncodeFilter - Standard Filter Strategy

public class StandardEncodeFilter 
  extends BaseEncodeFilter {
  // Creates new StandardEncodeFilter
  public StandardEncodeFilter()   {  }

  public void doFilter(javax.servlet.ServletRequest 
    servletRequest,javax.servlet.ServletResponse 
    servletResponse,javax.servlet.FilterChain 
    filterChain) 
  throws java.io.IOException, 
    javax.servlet.ServletException {

    String contentType = 
      servletRequest.getContentType();
    if ((contentType == null) || 
      contentType.equalsIgnoreCase(
        "application/x-www-form-urlencoded"))     {
      translateParamsToAttributes(servletRequest, 
        servletResponse);
    }

    filterChain.doFilter(servletRequest, 
      servletResponse);
  }

  private void translateParamsToAttributes(
    ServletRequest request, ServletResponse response)
  {
    Enumeration paramNames = 
        request.getParameterNames();

    while (paramNames.hasMoreElements())     {
      String paramName = (String) 
          paramNames.nextElement();

      String [] values;

      values = request.getParameterValues(paramName);
      System.err.println("paramName = " + paramName);
      if (values.length == 1)
        request.setAttribute(paramName, values[0]);
      else
        request.setAttribute(paramName, values);
    }
 }
}


Example 7.9 MultipartEncodeFilter - Standard Filter Strategy

public class MultipartEncodeFilter extends 
  BaseEncodeFilter {
  public MultipartEncodeFilter() { }
  public void doFilter(javax.servlet.ServletRequest 
    servletRequest, javax.servlet.ServletResponse 
    servletResponse,javax.servlet.FilterChain 
    filterChain)
  throws java.io.IOException, 
    javax.servlet.ServletException {
    String contentType = 
      servletRequest.getContentType();   
    // Only filter this request if it is multipart 
    // encoding                
    if (contentType.startsWith(
                "multipart/form-data")){
      try {
        String uploadFolder = 
          getFilterConfig().getInitParameter(
              "UploadFolder");
        if (uploadFolder == null) uploadFolder = ".";

        /** The MultipartRequest class is: 
        * Copyright (C) 2001 by Jason Hunter 
        * <jhunter@servlets.com>. All rights reserved. 
        **/
        MultipartRequest multi = new 
          MultipartRequest(servletRequest, 
                           uploadFolder,
                           1 * 1024 * 1024 );
        Enumeration params = 
                 multi.getParameterNames();
        while (params.hasMoreElements()) {
          String name = (String)params.nextElement();
          String value = multi.getParameter(name);
          servletRequest.setAttribute(name, value);
        }

        Enumeration files = multi.getFileNames();
        while (files.hasMoreElements()) {
          String name = (String)files.nextElement();
          String filename = multi.getFilesystemName(name);
          String type = multi.getContentType(name);
          File f = multi.getFile(name);
          // At this point, do something with the 
          // file, as necessary
        }
      }
      catch (IOException e)
      {
        LogManager.logMessage(
          "error reading or saving file"+ e);
      }
    } // end if
    filterChain.doFilter(servletRequest, 
                         servletResponse);
  } // end method doFilter()
}

The following excerpt in Example 7.10 is from the deployment descriptor for the Web application containing this example. It shows how these two filters are registered and then mapped to a resource, in this case a simple test servlet. Additionally, the sequence diagram for this example is shown in Figure 7.5.

Example 7.10 Deployment Descriptor - Standard Filter Strategy

.
.
.
<filter>
    <filter-name>StandardEncodeFilter</filter-name>
    <display-name>StandardEncodeFilter</display-name>
    <description></description>
    <filter-class> corepatterns.filters.encodefilter.
            StandardEncodeFilter</filter-class>
  </filter>
  <filter>
    <filter-name>MultipartEncodeFilter</filter-name>
    <display-name>MultipartEncodeFilter</display-name>
    <description></description>
    <filter-class>corepatterns.filters.encodefilter.
            MultipartEncodeFilter</filter-class>
    <init-param>
      <param-name>UploadFolder</param-name>
      <param-value>/home/files</param-value>
    </init-param>
 </filter>
.
.
.
<filter-mapping>
    <filter-name>StandardEncodeFilter</filter-name>
    <url-pattern>/EncodeTestServlet</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>MultipartEncodeFilter</filter-name>
    <url-pattern>/EncodeTestServlet</url-pattern>
  </filter-mapping>
.
.
.


Figure 7.5
Figure 7.5 Sequence diagram for Intercepting Filter, Standard Filter Strategy - encoding conversion example

The StandardEncodeFilter and the MultiPartEncodeFilter intercept control when a client makes a request to the controller servlet. The container fulfills the role of filter manager and vectors control to these filters by invoking their doFilter methods. After completing its processing, each filter passes control to its containing FilterChain, which it instructs to execute the next filter. Once both of the filters have received and subsequently relinquished control, the next component to receive control is the actual target resource, in this case the controller servlet.

Filters, as supported in version 2.3 of the servlet specification, also support wrapping the request and response objects. This feature provides for a much more powerful mechanism than can be built using the custom implementation suggested by the Custom Filter Strategy. Of course, a hybrid approach combining the two strategies could be custom built as well, but would still lack the power of the Standard Filter Strategy as supported by the servlet specification.

Base Filter Strategy

A base filter serves as a common superclass for all filters. Common features can be encapsulated in the base filter and shared among all filters. For example, a base filter is a good place to include default behavior for the container callback methods in the Declared Filter Strategy. Example 7.11 shows how this can be done.

Example 7.11 Base Filter Strategy

public class BaseEncodeFilter implements 
  javax.servlet.Filter {
  private javax.servlet.FilterConfig myFilterConfig;
        
  public BaseEncodeFilter()             {       }

  public void doFilter(javax.servlet.ServletRequest     
    servletRequest,javax.servlet.ServletResponse 
    servletResponse, javax.servlet.FilterChain 
    filterChain) throws java.io.IOException, 
    javax.servlet.ServletException {

    filterChain.doFilter(servletRequest, 
      servletResponse);
  }

  public javax.servlet.FilterConfig getFilterConfig() {
    return myFilterConfig; 
  }
    
  public void 
  setFilterConfig(javax.servlet.FilterConfig 
    filterConfig) {
    myFilterConfig = filterConfig;
  }
}

Template Filter Strategy

Using a base filter from which all others inherit (see "Base Filter Strategy" in this chapter) allows the base class to provide template method [Gof] functionality. In this case, the base filter is used to dictate the general steps that every filter must complete, while leaving the specifics of how to complete that step to each filter subclass. Typically, these would be coarsely defined, basic methods that simply impose a limited structure on each template. This strategy can be combined with any other filter strategy, as well. The listings in Example 7.12 and Example 7.13 show how to use this strategy with the Declared Filter Strategy.

Example 7.12 shows a base filter called TemplateFilter, as follows.

Example 7.12 Using a Template Filter Strategy

public abstract class TemplateFilter implements 
  javax.servlet.Filter {
  private FilterConfig filterConfig;

  public void setFilterConfig(FilterConfig fc) { 
    filterConfig=fc; 
  }

  public FilterConfig getFilterConfig()         { 
    return filterConfig; 
  }

  public void doFilter(ServletRequest request, 
    ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    // Common processing for all filters can go here 
    doPreProcessing(request, response, chain);

    // Common processing for all filters can go here
    doMainProcessing(request, response, chain);

    // Common processing for all filters can go here 
    doPostProcessing(request, response, chain);

    // Common processing for all filters can go here

    // Pass control to the next filter in the chain or 
    // to the target resource
    chain.doFilter(request, response);
  }
  public void doPreProcessing(ServletRequest request,   
    ServletResponse response, FilterChain chain) {
  }

  public void doPostProcessing(ServletRequest request, 
    ServletResponse response, FilterChain chain) {
  }

  public abstract void doMainProcessing(ServletRequest 
   request, ServletResponse response, FilterChain 
   chain);
}

Given this class definition for TemplateFilter, each filter is implemented as a subclass that must only implement the doMainProcessing method. These subclasses have the option, though, of implementing all three methods if they desire. Example 7.13 is an example of a filter subclass that implements the one mandatory method (dictated by our template filter) and the optional preprocessing method. Additionally, a sequence diagram for using this strategy is shown in Figure 7.6.

Example 7.13 Debugging Filter

public class DebuggingFilter extends TemplateFilter {
  public void doPreProcessing(ServletRequest req, 
    ServletResponse res, FilterChain chain) {
    //do some preprocessing here
  }

  public void doMainProcessing(ServletRequest req, 
    ServletResponse res, FilterChain chain) {
    //do the main processing;
  }
}

Figure 7.6
Figure 7.6 Intercepting Filter, Template Filter Strategy sequence diagram

In the sequence diagram in Figure 7.6, filter subclasses, such as DebuggingFilter, define specific processing by overriding the abstract doMainProcessing method and, optionally, doPreProcessing and doPostProcessing. Thus, the template filter imposes a structure to each filter's processing, as well as providing a place for encapsulating code that is common to every filter.

Consequences

  • Centralizes Control with Loosely Coupled Handlers
    Filters provide a central place for handling processing across multiple requests, as does a controller. Filters are better suited to massaging requests and responses for ultimate handling by a target resource, such as a controller. Additionally, a controller often ties together the management of numerous unrelated common services, such as authentication, logging, encryption, and so forth, while filtering allows for much more loosely coupled handlers, which can be combined in various combinations.

  • Improves Reusability
    Filters promote cleaner application partitioning and encourages reuse. These pluggable interceptors are transparently added or removed from existing code, and due to their standard interface, they work in any combination and are reusable for varying presentations.
  • Declarative and Flexible Configuration
    Numerous services are combined in varying permutations without a single recompile of the core code base.
  • Information Sharing is Inefficient
    Sharing information between filters can be inefficient, since by definition each filter is loosely coupled. If large amounts of information must be shared between filters, then this approach may prove to be costly.

Related Patterns

  • Front Controller
    The controller solves some similar problems, but is better suited to handling core processing.

  • Decorator [GoF]
    The Intercepting Filter pattern is related to the Decorator pattern, which provides for dynamically pluggable wrappers.
  • Template Method [GoF]
    The Template Method pattern is used to implement the Template Filter Strategy.
  • Interceptor [POSA2]
    The Intercepting Filter pattern is related to the Interceptor pattern, which allows services to be added transparently and triggered automatically.
  • Pipes and Filters [POSA1]
    The Intercepting Filter pattern is related to the Pipes and Filters pattern.

 

Contact Us © 2001-2002 Sun Microsystems, Inc. All Rights Reserved.

 

Left Curve
Java SDKs and Tools
Right Curve
Left Curve
Java Resources
Right Curve
JavaOne Banner Java 8 banner (182)