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.
§ Logging and authentication
§ Debugging and transformation of output for a specific client
§ Uncompressing and converting encoding scheme of input
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.
Figure 7.1 represents the Intercepting Filter pattern.
Participants and Responsibilities
Figure 7.2 represents the Intercepting Filter pattern.
The FilterManager manages filter processing. It creates the FilterChain with the appropriate filters, in the correct order, and initiates processing.
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.
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
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
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.
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.
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.
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
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
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
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
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
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.
Contact Us© 2001-2002 Sun Microsystems, Inc. All Rights Reserved.