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,
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
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 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.
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 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.
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
Contact Us© 2001-2002 Sun Microsystems,
Inc. All Rights Reserved.
|