Intercepting Filter Design Pattern in Java

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.
One of the best example for Intercepting Filter Pattern is Spring Security's DelegatingFilterProxy , which will intercept the HTTP request and do the authentication check. Spring security build on chain of filters.
Let's see how the Intercepting Filter Pattern solve the problem with examples. This pattern is divided into a number of sections for simplicity like problem, forces,solution,implementation etc.
Table of contents
Problem
Forces
Solution
Explanation
Structure - Class Diagram, Sequence Diagram
Participants and Responsibilities
Implementation
Consequences
Applicability
Real world examples
References

Problem

(Problem section describes the design issues faced by the developer)
Pre-processing 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. You want to intercept and manipulate a request and a response before and after the request is processed.

Forces

(This section describes Lists the reasons and motivations that affect the problem and the solution. The list of forces highlights the reasons why one might choose to use the pattern and provides a justification for using the pattern)
  • You want centralized, common processing across requests, such as checking the data-encoding scheme of each request, logging information about each request, or compressing an outgoing response.
  • You want pre and postprocessing components loosely coupled with core request-handling services to facilitate unobtrusive addition and removal.
  • You want pre and postprocessing components independent of each other and self contained to facilitate reuse.

Solution

(Here solution section describes the solution approach briefly and the solution elements in detail)
Use an Intercepting Filter as a pluggable filter to pre and postprocess requests and responses. A filter manager combines loosely coupled filters in a chain, delegating control to the appropriate filter. In this way, you can add, remove, and combine these filters in various ways without changing existing code.

Structure

Class Diagram


Sequence Diagram

Participants

Filter - Filter which will performs certain task prior or after execution of request by request handler. 
Filter Chain - Filter Chain carries multiple filters and help to execute them in defined order on target.
Target - Target object is the request handler  
Filter Manager - Filter Manager manages the filters and Filter Chain.  
Client - Client is the object who sends request to the Target object.

Implementation

Step 1 : Create Filter interface.
public interface Filter {
   public void execute(String request);
}
Step 2 : Create concrete filters - AuthenticationFilter, DebugFilter.
public class AuthenticationFilter implements Filter {
   public void execute(String request){
      System.out.println("Authenticating request: " + request);
   }
}
public class DebugFilter implements Filter {
   public void execute(String request){
      System.out.println("request log: " + request);
   }
}
Step 3 : Create Target.
public class Target {
   public void execute(String request){
      System.out.println("Executing request: " + request);
   }
}
Step 4 : Create FilterChain.
import java.util.ArrayList;
import java.util.List;

public class FilterChain {
   private List<Filter> filters = new ArrayList<Filter>();
   private Target target;

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

   public void execute(String request){
      for (Filter filter : filters) {
         filter.execute(request);
      }
      target.execute(request);
   }

   public void setTarget(Target target){
      this.target = target;
   }
}
Step 5 : Create FilterManager.
public class FilterManager {
   FilterChain filterChain;

   public FilterManager(Target target){
      filterChain = new FilterChain();
      filterChain.setTarget(target);
   }
   public void setFilter(Filter filter){
      filterChain.addFilter(filter);
   }

   public void filterRequest(String request){
      filterChain.execute(request);
   }
}
Step 6 : Create Client.
public class Client {
   FilterManager filterManager;

   public void setFilterManager(FilterManager filterManager){
      this.filterManager = filterManager;
   }

   public void sendRequest(String request){
      filterManager.filterRequest(request);
   }
}
Step 7 : Use the Client to demonstrate Intercepting Filter Design Pattern.
public class InterceptingFilterDemo {
   public static void main(String[] args) {
      FilterManager filterManager = new FilterManager(new Target());
      filterManager.setFilter(new AuthenticationFilter());
      filterManager.setFilter(new DebugFilter());

      Client client = new Client();
      client.setFilterManager(filterManager);
      client.sendRequest("HOME");
   }
}
Step 8 : Verify the output.
Authenticating request: HOME
request log: HOME
Executing request: HOME

Standard Filter Strategy

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.

javax.servlet.Filter(interface)

A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both. Filters perform filtering in the doFilter method. 
Every Filter has access to a FilterConfig object from which it can obtain its initialization parameters, a reference to the ServletContext which it can use, for example, to load resources needed for filtering tasks.
Filters are configured in the deployment descriptor of a web application.
Examples that have been identified for this design are
  1. Authentication Filters
  2. Logging and Auditing Filters
  3. Image conversion Filters
  4. Data compression Filters
  5. Encryption Filters
  6. Tokenizing Filters
  7. Filters that trigger resource access events
  8. XSL/T filters
  9. Mime-type chain Filter

javax.servlet.FilterChain(interface)

A FilterChain is an object provided by the servlet container to the developer giving a view into the invocation chain of a filtered request for a resource.
Filters use the FilterChain to invoke the next filter in the chain, or if the calling filter is the last filter in the chain, to invoke the resource at the end of the chain.

Examples

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 simplegetParameter()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.
Example 1 - 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;
  }
}
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 2- 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()
}
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>
The source code of Intercepting Filter Design Pattern is available on GitHub

Consequences

  • Centralizes control with loosely coupled handlers
  • Improves reusability
  • Declarative and flexible configuration
  • Information sharing is inefficient

Applicability

Use the Intercepting Filter pattern when
  • A system uses pre-processing or post-processing requests
  • A system should do the authentication/ authorization/ logging or tracking of request and then pass the requests to corresponding handlers
  • You want a modular approach to configuring pre-processing and post-processing schemes

Real world examples

References

Related Presentation Tier Posts

Free Spring Boot Tutorial | Full In-depth Course | Learn Spring Boot in 10 Hours


Watch this course on YouTube at Spring Boot Tutorial | Fee 10 Hours Full Course

Comments