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.
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,
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.
Common processing, such as checking the data-encoding scheme or logging information about each request, completes per request.
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 Intercepting Filter pattern class diagram
Participants and Responsibilities
Figure 7.2 represents the Intercepting Filter pattern.
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.
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 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 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 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 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.
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.
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.
Numerous services are combined in varying permutations without a single recompile of the core code base.
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.
The controller solves some similar problems, but is better suited to handling core processing.
The Intercepting Filter pattern is related to the Decorator pattern, which provides for dynamically pluggable wrappers.
The Template Method pattern is used to implement the Template Filter Strategy.
The Intercepting Filter pattern is related to the Interceptor pattern, which allows services to be added transparently and triggered automatically.
The Intercepting Filter pattern is related to the Pipes and Filters pattern.