Sun Java Solaris Communities My SDN Account Join SDN
 
J2EE

Sun Java Center J2EE Patterns

 

Decorating Filter

Context

System handles web requests. Request processing involves handling a variable number of system services.

Problem

Pre-processing and post-processing of a client web request and response are required.
A mechanism for adding and removing processing components to accomplish this goal in a flexible and unobtrusive manner is necessary.

Forces

  • Common system services processing completes per request. For example, the security service completes authentication and authorization checks, or a logging service logs information about each request.
  • Centralization of common services logic is desired.
  • Services should be easy to add or remove unobtrusively, so that they can be used in a variety of combinations that don't affect existing components such as:
    • Logging and authentication
    • Debugging and transformation of output (for a specific client)
    • Compression and authentication

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 pre 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 in order 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

The following class diagram represents the Decorating Filter pattern:

Participants & Responsibilities

The following sequence diagram represents the Decorating Filter pattern:

click to enlarge

Strategies

Dynamic Filter Strategy

Filter is implemented via a custom strategy defined by the developer. This is less flexible and powerful than the 'Declared Filter Strategy', which is preferred. The developer would use the Decorator [GoF] pattern to programmatically wrap filters around the core request processing logic. There may be a debugging filter which wraps an authentication filter. Here is an example of how one might create this mechanism programmatically:

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);
}

}

public class CoreProcessing implements Processor
{

private Processor target;
public CoreProcessing ()
{
this(null);
}

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

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

}

In the Servlet controller we might delegate to a method called 'processRequest' in order to handle requests:

public void processRequest(ServletRequest req,
ServletResponse res)
throws IOException, ServletException
{

Processor processors = new DebuggingFilter(
new AuthenticationFilter(
new CoreProcessing()));

processors.execute(req, res);

//Then dispatch to next resource,
// which is probably the View to display

dispatcher.dispatch(req, res);

}

Assuming that each processing component writes to stdout when it is executed, possible execution output is as follows:

Debugging filter pre-processing completed at this point...
Authentication filter processing completed at this point...
Core processing completed at this point...
Debugging filter post-processing completed at this point...

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 CoreProcessing class, as well as in any filter classes when we want to modify how we handle requests.

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. More importantly, we have no way to wrap the request and response objects. The preferred strategy, the 'Declared Filter Strategy' provides solutions to these issues.

 

Declared Filter Strategy

Filters are controlled declaratively, using a property file or a descriptor, such as that supported by the Servlet specification version 2.3.

This is the simplest and most elegant strategy, and is supported in version 2.3 of the Servlet specification. Vendor support for this version of the specification should be widely available by approximately late 2001. One could write portions of the infrastructure to support this strategy oneself, but it would not provide the same power and flexibility and would be nonstandard. Such an approach is described in the 'Dynamic Filter Strategy'.

The Servlet specification, version 2.3 will include a standard mechanism to build, add, and remove filters unobtrusively. A filter is built based on specified interfaces, and added/removed in a declarative manner, by modifying the deployment descriptor for a web application.

Here is an example of a possible filter, based on the final draft of the Servlet Specification, version 2.3:

public final class DebuggingFilter implements Filter

{

public void doFilter(ServletRequest req,
ServletResponse res, FilterChain chain)
throws IOException, ServletException
{
//Complete filter processing here...
//Pass control to the next filter
// in the chain or to the target resource
chain.doFilter(request, response);
}

}

Here is an excerpt of a possible filter configuration from a deployment descriptor for a web application:

<filter-mapping>
<filter-name>Debugging Filter</filter-name>
<servlet-name>ControllerServlet</servlet-name>
</filter-mapping>

In this case, our Debugging Filter will intercept control when a client makes a request to the ControllerServlet. When a client attempts to invoke the ControllerServlet, the container checks to see what filters are configured to wrap the target resource, finds the Debugging Filter, and vectors control to this filter by invoking its doFilter() method. After completing its processing, the filter passes along control to the next resource in the chain. Once again the container checks to see what filters are configured to wrap the target resource. If there was another filter configured in the deployment descriptor, this filter would receive control at this point. Since there is only one filter in the chain, the next resource to receive control will be the actual target resource, in this case the ControllerServlet.

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 'Dynamic 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 Declared filter strategy as supported by the Servlet specification version 2.3.

 

Consequences

  • Centralized Control
    Provides a central place to handle system services and business logic across multiple requests. The FrontController pattern also centralizes control, but it often ties together the management of numerous unrelated common services, such as authentication, logging, encryption, and so forth. (See "loose coupling" below.)
  • Improved Reusability
    Promotes cleaner application partitioning and encourages reuse. These pluggable decorators are easily and unobtrusively added to or removed from the existing request handling code. They can be used in any combination and they are easy to reuse for multiple presentations.
  • Declarative and Flexible Configuration
    Numerous services can be combined in many different ways without code changes to the existing core code base.
  • Loose Coupling/Strong Cohesion
    Promotes looser coupling, as well as stronger cohesion, among services, since each is written independently of the others.
  • 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, this approach may prove costly.

 

Related Patterns

  • FrontController
    The Front Controller solves some similar problems, but is often more powerful when used in combination with DecoratingFilter.
  • Decorator
    The DecoratingFilter pattern is related to the decorator pattern, which provides for dynamically pluggable wrappers.
  • Pipes and Filters
    The DecoratingFilter pattern is related to the pipes and filters pattern.

(c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.

Version 1.0 Beta