Using Filters

We will briefly describe some typical uses of filters. They should provide you with ideas for how filters can be used in your own applications. These examples demonstrate how filters provide a flexible, portable, and modular method of adding functionality to our web applications:

Filters can be used to implement the Adaptor architectural pattern, which can be useful when the output from one system does not match the input requirement of another system (particularly when the systems are not in our control). A filter can modify the request or response to adapt one system to another.

Logging and auditing filters can be added to a set of resources within a web application to measure traffic or enforce resource quotas.

Compression filters can be used to reduce the bandwidth used on expensive network connections. For example, a metropolitan radio packet network used for sending digital information to radio-equipped PDAs may be expensive. By using a compression filter on the server, along with a decompression library on the client, cost savings can be made.

Encryption and decryption filters can be used to bridge a network of requests over the Internet. Encryption algorithms could be changed simply by changing the filter used. For example, a gateway filter could be added to support a hardware-based public key encryption filter. If the decryption logic is contained in a proxy, the client can access the server securely (even over an unsecured network such as the Internet), without having the decryption logic built in to it.

Authentication and authorization filters can add security features to basic web services, which may have originally been written without authentication and authorization in mind. For example, we can quickly impose a fixed password to a set of web resources by adding a filter that implements the BASIC HTTP authentication protocol.

Transformation filters are useful for presenting multiple views of the same content. For example, we could use a language translation filter that detects the country of origin of a client and then translates the content before sending it to the client.

A Logging Filter

The first filter that we will build is just about as simple as a filter can get - it's intended to get us acquainted with the design, coding, and deployment stages common to all filters. The filter will log access to the underlying web resource; for example:

<date and time> Request Originated from IP <xxx.xxx.xxx.xxx(xxxx.xxx.xxx.xxx)>, using browser <browser's Agent ID> and accessed resource <URL of resource> and used <duration> ms

Information will be extracted from the request, which will allow us to log the originating IP address, the date and time of request, the type of browser that makes the request, and the time spent by the underlying resource processing the request:

The doFilter() method is where the filter processing logic resides. A ServletRequest object is passed in with all the details of the incoming request. The ServletResponse object passed in is to what the output (if any) must be written to. The filter is also obliged to call the doFilter() method of the FilterChain object passed in - this will pass the response and request (or their wrapper classes) downstream.

In our case, the doFilter() method performs pre-processing logic by storing the current time, and extracting the remote address, the remote hostname, the USER-AGENT header, and the URI of the request:

After the pre-processing logic is completed, we must call the downstream filters (and/or resources). In this case, we simply pass the incoming Request and Response objects that were passed in to us. As our filter doesn't modify the Request or Response objects, there's no need to create wrappers for them:

chain.doFilter(request, response);

After the downstream doFilter() call, we are ready to perform the post-processing logic. In this case, we simply write a log entry, reflecting the processed request. We could not have written this entry in the pre-processing logic as we needed to calculate the time the resource spent processing the request:

This XML contains data for two stock quotes. In a production application of course, XMLOutServlet would obtain its data via a live data feed, a JDBC data source, or other web services. However, so long as it outputs XML, the filtering logic will remain the same.

Compile all of the classes. Then we need to create the WAR containing the required files. You should have the following file and directory structure:

WEB-INF/ web.xml classes/filters/ XMLOutServlet LoggerFilter

Create the WAR using the following command:

jar cvf filters.war WEB-INF/*

Deploy the web application by moving filters.war to Tomcat's webapps folder, and restart the server.

Using the Filter

Before you access XMLOutServlet, check out the logs directory of Tomcat. You should find a log file with a name that follows the following scheme:

<hostname>_log_yyyy-mm-dd.txt

Where <hostname> is the local host name, and yyyy-mm-dd is the date of the log. This is the file that our logging filter will write to. Navigate to http://localhost:8080/filters/servlet/XMLOutServlet. The result you see will depend on whether your browser understands XML. I used Internet Explorer 6, which does:

Examine the end of the log file; you will find that it contains the output from our logging filter. It will be similar to this:

A XSLT Transformation Filter

Our second example filter will be more complex than the first, although we will be applying this filter to the same XMLOutServlet servlet. This filter will first check the browser type of the client and then:

If the client's browser is Internet Explorer, the XML output is passed directly back to the browser.

If the browser is any other type, we will assume that it cannot display XML properly. So, we will use an XSLT stylesheet to transform the XML into HTML, and then pass the HTML back to the client

Detecting the Browser Type

We will use the USER-AGENT header, which contains information on the browser version, vendor, and the operating system of the client to detect the browser type. Internet Explorer 6 on Windows 2000 sends the following USER-AGENT header:

mozilla/4.0 (compatible; msie 6.0; windows nt 5.0)

Unlike Internet Explorer, the USER-AGENT headers of other browsers do not contain the msie string. We'll use this fact to detect if the browser is Internet Explorer. For example, the USER-AGENT header for Netscape 6.1 on Windows 98 is:

Converting XML to HTML

The XSLT stylesheet used to transform the content is named stockquotes.xsl, and is stored in an xsl directory in the root directory of the web application. It will transform the output from our XMLOutServlet servlet into an HTML page:

You can learn more about how to use XML and XSLT in Professional Java XML(ISBN 1-861004-01-X) and Java XML Programmers Reference(ISBN 1-861005-20-2).

Content Substitution Filters

In many filters, such as our earlier logging example, the Request and Response objects that we pass down are the same that are passed into the doFilter() method. Filters that replace content do not pass the same Request and Response objects downstream. Instead, they pass a wrapper Request object that can intercept calls by downstream components in order to provide access to a modified version of the request data.

In filters that modify or transform the response, we also pass a wrapper Response object that will capture the response from the next downstream component in a memory buffer. After the chained call returns, this buffer can be examined, and the transformed or replaced output written to the actual response. Our XSLT transform filter will do exactly this.

Implementing the Filter

This XSLT filter requires an XSLT parser. The JAXP library used by Tomcat will access the Xalan parser by default so you should download the latest version of xalan.jar from http://xml.apache.org/xalan-j/index.html and place it in %CATALINA_HOME%\common\lib\.

Before we can implement our filter, we need to create a wrapper class for a buffer:

The buffer allows us to easily convert between the output capture buffer and a string - allowing us to work with the captured output. This is the buffer that the filter will use to capture the output of the web resource (or downstream filter). In our case, it will be the output of XMLOutServlet:

In the init() method we use JAXP to create a XSLT template for transformations. The XSLT template is based on an XSLT stylesheet source file, which is specified by a initialization parameter in the deployment descriptor. The getInitParameter() method of the FilterConfig object can be used to retrieve these initialization parameter values:

The doFilter() method contains the core logic of the filter. The first section contains the pre-processing logic, in which we decode the USER-AGENT header of the request, and determine if the request is from an Internet Explorer browser:

The exact way we call downstream filters will depend on whether we are dealing with Internet Explorer or not. If it is not Internet Explorer, we will need to transform the output from XML to HTML. This is done by creating an instance of the OutputCapturer wrapper class, and then handing it downstream:

If the request is from Internet Explorer, we simply pass the incoming request and response. The output from XMLOutServlet is not touched at all, and the browser can be used to view the resulting XML data:

} else {chain.doFilter(request, response);}}

The destroy() method sets the instance variables to null, which will release the associated objects and enable a container to re-use this filter instance (if the functionality is implemented) - avoiding the overhead of destroying and creating a new instance:

The filter mapping we make in this case is done via a <servlet-name> element, which will associate a specific servlet (XMLOutServlet) with the filter. The filter will only be activated when this servlet is accessed:

Deploy the web application by copying filters.war to Tomcat's webapps folder, and restarting the server.

Using the Filter

Use Internet Explorer to navigate to http://localhost:8080/filters/XMLOutServlet to access the servlet (and our filter). Our filter will have detected the browser, and passed the XML output straight through, so we'll see the same output as we did during our logging example.

Now, try accessing the same URL using another type of browser (I used Netscape 6.2). Our SmartXSLFilter will detect the non-IE browser, and perform the XML to HTML conversion before sending back the response. The output will look similar to this: