![]() |
|
|
![]()
|
Intercepting FilterContextThe 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. ProblemPreprocessing 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
SolutionCreate 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. StructureFigure 7.1 represents the Intercepting Filter pattern.
Participants and ResponsibilitiesFigure 7.2 represents the Intercepting Filter pattern.
FilterManagerThe FilterManager manages filter processing. It creates the FilterChain with the appropriate filters, in the correct order, and initiates processing. FilterChainThe FilterChain is an ordered collection of independent filters. FilterOne, FilterTwo, FilterThreeThese are the individual filters that are mapped to a target. The FilterChain coordinates their processing. TargetThe Target is the resource requested by the client. StrategiesCustom Filter StrategyFilter 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); } } 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 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.
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.
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); } } 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 StrategyFilters 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 One filter handles the standard form encoding of type 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; } } 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); } } } 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> . . . 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 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 StrategyA 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 StrategyUsing 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 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; } } In the sequence diagram in Figure 7.6, filter subclasses, such as DebuggingFilter,
define specific processing by overriding the abstract Consequences
Related Patterns
|
|||||||||||||||||||||||
© 2001, Core J2EE Patterns, All Rights Reserved. |