Solution
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.
As with other systems where there is a significant number of duplicated tasks, it’s a good idea to take that duplicated processing, factor it out, and make it coarse grained enough to apply broadly. This is what pluggable filters accomplish.
In effect, you are able to decorate the main processing with a variety of common services, such as logging and debugging. Filters are independent of the main application code, and you can add or remove them declaratively.
Filters are controlled declaratively using a deployment descriptor, as described in the "Servlet Specification,” version 2.3. The “Servlet 2.3 Specification” includes a standard mechanism for building filter chains and unobtrusively adding and removing filters from those chains. A deployment configuration file sets up a chain of filters and can include a mapping of specific URLs to this filter chain. When a client requests a resource that matches the configured URL mapping, the filters in the chain are invoked before (preprocessing) and/or after (postprocessing) the invocation of the requested target resource.
Structure
Figure 6.1 represents the Intercepting Filter class diagram.
Figure 6.1 Intercepting Filter Class Diagram
Participants and Responsibilities
Figure 6.2 represents the Intercepting Filter sequence diagram.
Figure 6.2 Intercepting Filter Sequence Diagram
Client
The Client sends a request to the FilterManager.
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
FilterOne and FilterTwo represent individual filters that are mapped to a Target. The FilterChain coordinates the processing of filters.
Target
The Target is the resource requested by the client.
Strategies
Standard Filter Strategy
Filters are controlled declaratively using a deployment descriptor, as described in the servlet specification, version 2.3. The Servlet 2.3 Specification includes a standard mechanism for building filter chains and unobtrusively adding and removing filters from those chains. Filters are based upon a standard interface, and are loosely coupled to each other, as well as to the target resources for which they perform pre or postprocessing. Configure each filter declaratively by modifying the deployment descriptor for a web application.
The example for this strategy is a filter that preprocesses requests of any encoding type such that the core request-handling code can handle each request.
Why might this be necessary? An example is HTML forms that include a file upload use a different encoding type than that of basic forms. As a result, form data that accompanies the upload is not available via simple getParameter() invocations. In order to get at this form data, the example includes two filters that preprocess requests, translating all encoding types into a single consistent format. The format has all form data available as request attributes.
Each filter handles a different encoding type:
- One filter handles the standard form encoding of type application/ x-www-form-urlencoded
- 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 6.1 through Example 6.4 provide code for this example, and Figure 6.3 shows the sequence diagram.
The base filter, shown in Example 6.1, provides default behavior for the Standard Filter callback methods.
Example 6.1 Base Filter – Standard Filter Strategy
1 public class BaseEncodeFilter implements javax.servlet.Filter { 2 3 private javax.servlet.FilterConfig filterConfig; 4 5 public void doFilter( 6 javax.servlet.ServletRequest servletRequest, 7 javax.servlet.ServletResponse servletResponse, 8 javax.servlet.FilterChain filterChain) 9 throws java.io.IOException, 10 javax.servlet.ServletException { 11 12 filterChain.doFilter(servletRequest, servletResponse); 13 } 14 15 protected javax.servlet.FilterConfig getFilterConfig() { 16 return filterConfig; 17 } 18 19 public void destroy() { } 20 21 public void init(javax.servlet.FilterConfig filterConfig) 22 throws javax.servlet.ServletException { 23 this.filterConfig = filterConfig; 24 } 25 26 }
Example 6.2 shows a filter that translates requests using the common application form encoding scheme.
Example 6.2 StandardEncodeFilter – Standard Filter Strategy
1 public class StandardEncodeFilter extends BaseEncodeFilter { 2 // Creates new StandardEncodeFilter 3 public StandardEncodeFilter() { } 4 5 public void doFilter(javax.servlet.ServletRequest 6 servletRequest,javax.servlet.ServletResponse 7 servletResponse,javax.servlet.FilterChain filterChain) 8 throws java.io.IOException, 9 javax.servlet.ServletException { 10 11 String contentType = servletRequest.getContentType(); 12 if ((contentType == null) || contentType.equalsIgnoreCase( 13 "application/x-www-form-urlencoded")) { 14 translateParamsToAttributes(servletRequest, servletResponse); 15 } 16 17 filterChain.doFilter(servletRequest, servletResponse); 18 } 19 20 private void translateParamsToAttributes( 21 ServletRequest request, ServletResponse response) { 22 Enumeration paramNames = request.getParameterNames(); 23 24 while (paramNames.hasMoreElements()) { 25 String paramName = (String) paramNames.nextElement(); 26 String [] values; 27 values = request.getParameterValues(paramName); 28 if (values.length == 1) 29 request.setAttribute(paramName, values[0]); 30 else 31 request.setAttribute(paramName, values); 32 } 33 } 34 }
Example 6.3 shows the filter that handles the translation of requests. The requests use the multipart form encoding scheme. The code for these filters is based on the final draft of the servlet specification, version 2.3. Both filters inherit from a base filter (see Base Filter Strategy).
Example 6.3 MultipartEncodeFilter – Standard Filter Strategy
1 public class MultipartEncodeFilter extends BaseEncodeFilter { 2 public MultipartEncodeFilter() { } 3 public void doFilter( 4 javax.servlet.ServletRequest servletRequest, 5 javax.servlet.ServletResponse servletResponse, 6 javax.servlet.FilterChain filterChain) 7 throws java.io.IOException, 8 javax.servlet.ServletException { 9 10 String contentType = servletRequest.getContentType(); 11 // Only filter this request if it is multipart 12 // encoding 13 if (contentType.startsWith("multipart/form-data")) { 14 try { 15 String uploadFolder = getFilterConfig(). 16 getInitParameter("UploadFolder"); 17 if (uploadFolder == null) 18 uploadFolder = "."; 19 /** The MultipartRequest class is: 20 * Copyright (C) 2001 by Jason Hunter 21 * <jhunter@servlets.com>. All rights reserved. 22 **/ 23 MultipartRequest multi = new MultipartRequest( 24 servletRequest, uploadFolder, 1 * 1024 * 1024 ); 25 Enumeration params = multi.getParameterNames(); 26 while (params.hasMoreElements()) { 27 String name = (String)params.nextElement(); 28 String value = multi.getParameter(name); 29 servletRequest.setAttribute(name, value); 30 } 31 Enumeration files = multi.getFileNames(); 32 while (files.hasMoreElements()) { 33 String name = (String)files.nextElement(); 34 String filename = multi.getFilesystemName(name); 35 String type = multi.getContentType(name); 36 File f = multi.getFile(name); 37 // At this point, do something with the 38 // file, as necessary 39 } 40 } catch (IOException e) { 41 LogManager.logMessage("error reading or saving file"+ e); 42 } 43 } // end if 44 filterChain.doFilter(servletRequest, servletResponse); 45 } // end method doFilter() 46 }
The excerpt in Example 6.4 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 it’s a simple test servlet.
Example 6.4 Deployment Descriptor – Standard Filter Strategy
1 . 2 . 3 . 4 <filter> 5 <filter-name>StandardEncodeFilter</filter-name> 6 <display-name>StandardEncodeFilter</display-name> 7 <description></description> 8 <filter-class> corepatterns.filters.encodefilter. 9 StandardEncodeFilter</filter-class> 10 </filter> 11 <filter> 12 <filter-name>MultipartEncodeFilter</filter-name> 13 <display-name>MultipartEncodeFilter</display-name> 14 <description></description> 15 <filter-class>corepatterns.filters.encodefilter. 16 MultipartEncodeFilter</filter-class> 17 <init-param> 18 <param-name>UploadFolder</param-name> 19 <param-value>/home/files</param-value> 20 </init-param> 21 </filter> 22 . 23 . 24 . 25 <filter-mapping> 26 <filter-name>StandardEncodeFilter</filter-name> 27 <url-pattern>/EncodeTestServlet</url-pattern> 28 </filter-mapping> 29 <filter-mapping> 30 <filter-name>MultipartEncodeFilter</filter-name> 31 <url-pattern>/EncodeTestServlet</url-pattern> 32 </filter-mapping> 33 . 34 . 35 .
The sequence diagram for this example is shown in Figure 6.3.
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 directs control to these filters by invoking their doFilter methods. After completing its processing, each filter passes control to its containing FilterChain. The filter instructs its FilterChain to execute the next filter. Once both 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 lets you build a much more powerful mechanism than could be built using the custom implementation suggested by the Custom Filter strategy. Of course, you can custom build a hybrid approach combining the two strategies but this approach would still lack the power of the Standard Filter strategy, as supported by the servlet specification.
Figure 6.3 Intercepting Filter, Standard Filter Strategy – Encoding Conversion Example
Custom Filter Strategy
Implement the Filter with your own custom strategy. This is less flexible and less powerful than the preferred Standard Filter strategy that is available in containers supporting the 2.3 servlet specification and later. This Custom Filter strategy is less powerful because it doesn’t support wrapping request and response objects in a standard and portable way. Additionally, the request object cannot be modified, and you must introduce some sort of buffering mechanism if filters are to control the output stream.
To implement the Custom Filter strategy, you could use the Decorator pattern [GoF] to wrap filters around the core request-processing logic. For example, apply a debugging filter that wraps an authentication filter.
Example 6.5 and Example 6.6 show how you could create this mechanism programmatically.
Example 6.5 Implementing a Filter – Debugging Filter
1 public class DebuggingFilter implements Processor { 2 private Processor nextProcessor; 3 4 public DebuggingFilter(Processor nextProcessor) { 5 this.nextProcessor = nextProcessor; 6 } 7 8 public void execute(ServletRequest request, 9 ServletResponse response) 10 throws IOException, ServletException { 11 // Do some filter processing here, such as 12 // displaying request parameters 13 14 nextProcessor.execute(request, response); 15 } 16 }
Example 6.6 Handling Requests
1 public void processRequest(ServletRequest req, ServletResponse res) 2 throws IOException, ServletException { 3 Processor processors = new DebuggingFilter( 4 new AuthenticationFilter(new CoreProcessor())); 5 processors.execute(req, res); 6 7 //Then dispatch to next resource, which is probably 8 // the View to display 9 dispatcher.dispatch(req, res); 10 } 11
In the servlet controller, delegate to a method called ProcessRequest to handle incoming requests, as shown in Example 6.7.
Example 6.7 Implementing a Filter – Core Processor
1 public class CoreProcessor implements Processor { 2 3 public CoreProcessor() { } 4 5 public void execute(ServletRequest req, ServletResponse res) 6 throws IOException, ServletException { 7 //Do core processing here 8 } 9 }
For example purposes only, imagine that each processing component writes to standard output when it is executed. Example 6.8 shows the possible execution output.
Example 6.8 Messages Written to Standard Output
1 Debugging filter preprocessing completed... 2 Authentication filter processing completed... 3 Core processing completed... 4 Debugging filter post-processing completed...
Example 6.8 shows standard output when 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 you encapsulate the core processing you want to complete for each request. Given this design, you will need to change the code in the CoreProcessor class, as well as in any filter classes, when you want to modify how you handle requests.
Figure 6.4 is a sequence diagram describing the flow of control for the filter code of Example 6.5, Example 6.6, and Example 6.8.
Notice that with a Decorator implementation, each filter invokes on the next filter directly, though using a generic interface. You could implement this strategy a different way 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.
Figure 6.4 Sequence Diagram for Custom Filter Strategy, Decorator Implementation
This design approximates a servlet 2.3-compliant implementation, though it is still a custom strategy. Example 6.9 is just such a FilterManager class, which creates a FilterChain; the FilterChain is shown in Example 6.10. The FilterChain adds filters to the chain in the appropriate order, processes the filters, and finally processes the target resource. (For the sake of brevity, adding filters to the chain is done in the FilterChain constructor, but would normally be done in place of the comment.)
Figure 6.5 is a sequence diagram for this code.
Example 6.9 FilterManager – Custom Filter Strategy
1 public class FilterManager { 2 public void processFilter(Filter target, 3 javax.servlet.http.HttpServletRequest request, 4 javax.servlet.http.HttpServletResponse response) 5 throws javax.servlet.ServletException, 6 java.io.IOException { 7 8 FilterChain filterChain = new FilterChain(); 9 // The filter manager builds the filter chain here if necessary 10 // Pipe request through Filter Chain 11 filterChain.processFilter(request, response); 12 13 //process target resource 14 target.execute(request, response); 15 } 16 }
Figure 6.5 Sequence Diagram for Custom Filter Strategy, Nondecorator Implementation
Example 6.10 FilterChain – Custom Filter Strategy
1 public class FilterChain { 2 // filter chain 3 private List myFilters = new ArrayList(); 4 5 // Creates new FilterChain 6 public FilterChain() { 7 // plug-in default filter services for example 8 // only. This would typically be done in the 9 // FilterManager, but is done here for example 10 // purposes 11 addFilter(new DebugFilter()); 12 addFilter(new LoginFilter()); 13 addFilter(new AuditFilter()); 14 } 15 16 public void processFilter( 17 javax.servlet.http.HttpServletRequest request, 18 javax.servlet.http.HttpServletResponse response) 19 throws javax.servlet.ServletException, 20 java.io.IOException { 21 22 Filter filter; 23 // apply filters 24 Iterator filters = myFilters.iterator(); 25 while (filters.hasNext()) { 26 filter = (Filter)filters.next(); 27 // pass request & response through various 28 // filters 29 filter.execute(request, response); 30 } 31 } 32 33 public void addFilter(Filter filter) { 34 myFilters.add(filter); 35 } 36 }
You can’t create very flexible or powerful filters with this strategy. Instead, use the Standard Filter strategy where possible. Filters are added and removed programmatically. While you could write a proprietary mechanism for handling adding and removing filters via a configuration file, you 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
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 Standard Filter strategy. Example 6.11 shows one way to do this.
Example 6.11 Base Filter Strategy
1 public class BaseEncodeFilter implements javax.servlet.Filter { 2 private javax.servlet.FilterConfig filterConfig; 3 4 public BaseEncodeFilter() { } 5 6 public void init(javax.servlet.FilterConfig filterConfig) { 7 this.filterConfig = filterConfig; 8 } 9 10 public void doFilter(javax.servlet.ServletRequest servletRequest, 11 javax.servlet.ServletResponse servletResponse, 12 javax.servlet.FilterChain filterChain) 13 throws java.io.IOException, 14 javax.servlet.ServletException { 15 16 filterChain.doFilter(servletRequest, servletResponse); 17 } 18 19 protected javax.servlet.FilterConfig getFilterConfig() { 20 return filterConfig; 21 } 22 }
Template Filter Strategy
A key benefit of using this strategy is that it abstracts away the details of the underlying servlet API and focuses on implementing preprocessing and postprocessing logic. This means you no longer need to deal with the semantics of method invocations such as chain.doFilter(req, res), but rather simply with method invocations such as doPreProcessing(req, res), which are much more intuitive.
This strategy builds on the Base Filter strategy, which simply describes using a base class to encapsulate all the details of the filter API (see Base Filter Strategy on page 158). The Template Filter strategy uses this base class to provide Template Method [Gof] functionality. In this case, the base filter dictates the general steps that every filter must perform, while leaving the specifics of how to complete that step to each filter subclass. This approach provides the inversion of control that is common in frameworks, meaning that the superclass (or base class) dictates the flow of control to its subclasses. Typically, the methods of the superclass are coarsely defined, basic methods that simply impose a limited structure on each template.
You can combine this strategy with any other filter strategy. Example 6.12 and Example 6.13 show how to use this strategy with the Declared Filter strategy.
Example 6.12 shows a base filter called TemplateFilter, as follows.
Example 6.12 Using a Template Filter Strategy
1 public abstract class TemplateFilter implements javax.servlet.Filter { 2 private FilterConfig filterConfig; 3 public void init(FilterConfig filterConfig) throws ServletException { 4 this.filterConfig = filterConfig; 5 } 6 7 protected FilterConfig getFilterConfig() { 8 return filterConfig; 9 } 10 11 public void doFilter(ServletRequest request, 12 ServletResponse response, FilterChain chain) 13 throws IOException, ServletException { 14 15 // Preprocessing for each filter 16 doPreProcessing(request, response); 17 18 // Pass control to the next filter in the chain or 19 // to the target resource. This method invocation is what logically 20 // demarcates preprocessing from postprocessing. 21 chain.doFilter(request, response); 22 23 // Post-processing for each filter 24 doPostProcessing(request, response); 25 } 26 public abstract void doPreProcessing(ServletRequest request, 27 ServletResponse response) { } 28 29 public abstract void doPostProcessing(ServletRequest request, 30 ServletResponse response) { } 31 32 public void destroy() { } 33 }
Given this class definition for TemplateFilter, each filter is implemented as a subclass that implements only the doPreProcessing and doPostProcessing methods. These subclasses have the option, though, of implementing all three methods. Example 6.13 is an example of a filter subclass that implements the two mandatory methods (dictated by the superclass template filter).
Example 6.13 Logging Filter
1 public class LoggingFilter extends TemplateFilter { 2 public void doPreProcessing(ServletRequest req, ServletResponse res) { 3 //do some preprocessing here, such as logging some information about 4 // the request before it’s been handled. 5 } 6 7 public void doPostProcessing(ServletRequest req, ServletResponse res){ 8 // do some post-processing here, such as logging some information 9 // about the request and response after the request has been handled 10 // and the response generated. 11 } 12 }
In the sequence diagram in Figure 6.6, filter subclasses, such as LoggingFilter, define specific processing by overriding the abstract doPreProcessing and doPostProcessing methods. As a result, the template filter imposes a flow of control upon each filter and focuses filter developers on the semantics of writing pre and postprocessing logic. Finally, since it is a base filter, it also provides a place for encapsulating code that is common to every filter.
A sequence diagram for this strategy is shown in Figure 6.6.
Figure 6.6 Intercepting Filter, Template Filter Strategy Sequence Diagram
Web Service Message-Handling Strategies
While using Intercepting Filter is common in the presentation tier, certain Intercepting Filter strategies are relevant in other tiers, as well. The Custom SOAP Filter and JAX-RPC Filter message-handling strategies are examples of filters used in the client, presentation, and integration tiers to handle pre and postprocessing of web service requests.
When loosely coupled, these chained filters are used to handle and manipulate web service requests and are often referred to as message handlers. These filters intercept incoming messages and perform pre and postprocessing on information in the message.
Similarly, the filters act on outgoing responses. For example, a message handler might validate a digital signature, sign a message, or log information about a message. Messages must be serialized from XML to Java and vice versa, as each message enters and leaves the handler mechanism.
The SOAP With Attachments API for Java, or SAAJ [SAAJ], allows developers to work with SOAP messages and provides support for building pre and postprocessing filters. Use SOAP message handlers judiciously; when you do use them, they provide a powerful processing mechanism. For more coverage of web services, see Application Controller (205) and Web Service Broker (557).
Custom SOAP Filter Strategy Web Service
This strategy is typically invoked as part of Application Controller’s (205) Custom SOAP Message-Handling strategy (230). A Context Object (181) is used to share details of the SOAP message between handlers, since one handler does not directly reference another.
The key differentiator between this strategy, shown in Figure 6.7 and Figure 6.8, and the JAX-RPC Filter strategy in Figure 6.9, is that in this strategy you write a custom handler to fulfill the participant responsibilities, using a SOAP library, such as SAAJ.
Figure 6.7 Custom SOAP Filter Strategy Class Diagram
Figure 6.8 Custom SOAP Filter Strategy Sequence Diagram
JAX-RPC Filter Strategy Web Service
This strategy is typically invoked as part of Application Controller’s (205) JAX-RPC Message-Handling strategy (235). A Context Object (181) is used to share details of the SOAP message between handlers, since one handler does not directly reference another.
The key differentiator between this strategy and the Custom SOAP Filter strategy is that in this strategy most of the participant responsibilities are performed by the JAX-RPC runtime engine. The handlers are implemented either on the client side of the RPC invocation, on the server side of the RPC invocation, or on both sides. Thus, you can insert handlers on either side of the network connection. You build message handlers with JAX-RPC using SAAJ, which offers powerful processing capabilities when used judiciously.
Figure 6.9 JAX-RPC Filter Strategy Sequence Diagram