Using Filters

 < Free Open Study > 



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:

    2001-11-12 11:26:40  Request Orginiated from IP 32.33.23.33    (remote.access.com), using browser (mozilla/4.0 (compatible; msie 6.0;    windows nt 5.0)) and accessed resource /filters/servlet/BasicServlet and    used 40 ms 

The general form of the log entry is:

    <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:

    package filters;    import java.io.*;    import javax.servlet.*;    import javax.servlet.http.*;    public final class LoggerFilter implements Filter {      private FilterConfig filterConfig = null; 

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:

      public void doFilter(ServletRequest request, ServletResponse response,                           FilterChain chain)          throws IOException, ServletException {        long startTime = System.currentTimeMillis();        String remoteAddress = request.getRemoteAddr();        String remoteHost = request.getRemoteHost();        HttpServletRequest myReq = (HttpServletRequest) request;        String reqURI = myReq.getRequestURI();        String browserUsed = myReq.getHeader("User-Agent").toLowerCase(); 

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:

        filterConfig.getServletContext().log(          "Request Originated from IP " + remoteAddress + "(" + remoteHost +          "), using browser (" + browserUsed + ") and accessed resource " +          reqURI + " and used " + (System.currentTimeMillis() - startTime) +          " ms"        );      } 

In the destroy() method we release the FilterConfig reference:

      public void destroy() {        this.filterConfig = null;      } 

In the init() method the container passes in a FilterConfig object, which we store for later use:

      public void init(FilterConfig filterConfig) {        this.filterConfig = filterConfig;      }    } 

Now, let's turn our attention to the web resource we will apply our filter to. We'll create a simple servlet named XMLOutServlet, which will simply return the following XML when invoked:

    <?xml version="1.0" ?>    <quote.set>      <stock.quote><stock>IBM</stock><price>100.20</price></stock.quote>      <stock.quote><stock>SUNW</stock><price>28.20</price></stock.quote>    </quote.set> 

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.

The code for the servlet is straightforward:

    package filters;    import java.io.*;    import javax.servlet.*;    import javax.servlet.http.*;    public class XMLOutServlet extends HttpServlet {      public void doGet(HttpServletRequest request,                        HttpServletResponse response)          throws ServletException, IOException {        PrintWriter out = response.getWriter();        out.println("<?xml version=\"1.0\" ?>");        out.println("<quote.set>");        out.println("<stock.quote><stock>IBM</stock>" +                    "<price>100.20</price></stock.quote>");        out.println("<stock.quote><stock>SUNW</stock>" +                    "<price>28.20</price></stock.quote>");        out.println("</quote.set>");      }    } 

Deploying the Filter

The deployment descriptor, web.xml, must contain the filter definition and mapping information before the filter can be used:

    <?xml version="1.0" encoding="ISO-8859-1"?>    <!DOCTYPE web-app PUBLIC              "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"              "http://java.sun.com/dtd/web-app_2_3.dtd">    <web-app> 

The name of the filter is set to Logger:

      <filter>        <filter-name>Logger</filter-name>        <filter-class>filters.LoggerFilter</filter-class>      </filter> 

The filter is mapped to all the resources served within this application context:

      <filter-mapping>        <filter-name>Logger</filter-name>        <url-pattern>/*</url-pattern>      </filter-mapping>      <servlet>        <servlet-name>XMLOutServlet</servlet-name>        <servlet-class>filters.XMLOutServlet</servlet-class>      </servlet>    </web-app> 

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:

click to expand

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:

click to expand

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:

    mozilla/5.0 (windows; u; win98; en-us; rv:0.9.2) gecko/20010726    netscape6/6.1 

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:

    <?xml version="1.0"?>    <xsl:stylesheet xmlns:xsl=        "http://www.w3.org/1999/XSL/Transform" version="1.0">      <xsl:template match="/">        <html>          <head>            <title>Wrox Stock Quote Page</title>          </head>          <body>            <h1>Wrox Quote Service</h1>            <table border="1">              <tr>                <td width="100">                  <b>Symbol</b>                </td>                <td width="100">                  <b>Price</b>                </td>              </tr>              <xsl:for-each select="quote.set/stock.quote">                <tr>                  <td><xsl:value-of select="stock"/></td>                  <td><xsl:value-of select="price"/></td>                </tr>              </xsl:for-each>            </table>          </body>        </html>      </xsl:template>    </xsl:stylesheet> 

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:

    package filters;    import java.io.*;    import javax.servlet.http.*;    public class OutputCapturer extends HttpServletResponseWrapper {      private CharArrayWriter 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:

      public OutputCapturer(HttpServletResponse resp) {        super(resp);        buffer = new CharArrayWriter();      } 

Downstream filters and resources will actually write their output into the buffer via a call to getWriter(). This is how the output is effectively 'captured':

      public PrintWriter getWriter() {        return new PrintWriter(buffer);      } 

The toString() method provides an easy way to access the buffer containing the captured output:

      public String toString() {        return buffer.toString();      }    } 

The filter class is named SmartXSLFilter and has three instance variables:

  • filterConfig - stores the context and instance information passed in from the container

  • xsltFactory - stores the JAXP XSLT transformation factory class used in this filter instance

  • xsltTemplates - stores the pre-loaded XSLT transformation template used in this filter instance

The filter's init() method will initialize these instance variables with the appropriate references and the destroy() method will release them:

    package filters;    import java.io.*;    import javax.servlet.*;    import javax.servlet.http.*;    import javax.xml.transform.*;    import javax.xml.transform.stream.*;    public final class SmartXSLFilter implements Filter {      private FilterConfig filterConfig = null;      private TransformerFactory xsltFactory = null;      private Templates xsltTemplates = null; 

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:

      public void init(FilterConfig filterConfig) {        this.filterConfig = filterConfig;        this.xsltFactory = TransformerFactory.newInstance();        String xsltfile = filterConfig.getInitParameter("xsltfile"); 

The template object allows the transformation to occur efficiently. By creating a template based on the XSLT source, we will not need to re-read and re-process the template for each incoming request:

        try {          this.xsltTemplates = xsltFactory.newTemplates(new StreamSource(            this.filterConfig.getServletContext().getRealPath(xsltfile)));        } catch (Exception ex) {          this.filterConfig.getServletContext()            .log("SmartXSLFilter - cannot create template - init failed - " +                 ex.toString());        }      }    } 

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:

      public void doFilter(ServletRequest request,                           ServletResponse response,                           FilterChain chain)                           throws IOException, ServletException {        String browserUsed =          ((HttpServletRequest) request).getHeader("User-Agent").toLowerCase();        boolean isMSIE = (browserUsed.indexOf("msie") >= 0); 

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 (!isMSIE) {          PrintWriter realOutput = response.getWriter();          OutputCapturer myCapture =                         new OutputCapturer((HttpServletResponse) response);          chain.doFilter (request, myCapture); 

After the chained call, myCapture now contains the output of XMLOutServlet. We will now transform this XML into HTML using the XSLT template that we obtained during initialization:

          try {            Source xfrmSrc =                     new StreamSource(new StringReader(myCapture.toString()));            Transformer tpXfrmer = xsltTemplates.newTransformer();            CharArrayWriter finalOut = new CharArrayWriter();            StreamResult xfrmResult = new StreamResult(finalOut);            tpXfrmer.transform(xfrmSrc, xfrmResult); 

The transformed output is stored in finalOut, and we use this to write the actual response:

            response.setContentLength(finalOut.toString().length());            realOutput.write(finalOut.toString());            filterConfig.getServletContext().log(                              "SmartXSLFilter activated - completed transform");          } catch (Exception ex) {            filterConfig.getServletContext().log(              "SmartXSLFilter - XSLT transformation failed - " + ex.toString());          } 

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:

      public void destroy() {        this.filterConfig = null;        this.xsltFactory = null;        this.xsltTemplates = null;      }    } 

Deploying the Filter

To configure the filter we need to add the filter definition and mapping information to web.xml:

    <?xml version="1.0" encoding="ISO-8859-1"?>    <!DOCTYPE web-app      PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"      "http://java.sun.com/dtd/web-app_2_3.dtd">    <web-app>      <filter>        <filter-name>XSLTFilter</filter-name>        <filter-class>          filters.SmartXSLFilter        </filter-class> 

An initialization parameter named xsltfile is given a value of /xsl/stockquotes.xsl (which is the path to the XSLT stylesheet that the filter uses):

        <init-param>          <param-name>xsltfile</param-name>          <param-value>/xsl/stockquotes.xsl</param-value>        </init-param>      </filter> 

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:

      <filter-mapping>        <filter-name>XSLTFilter</filter-name>        <servlet-name>XMLOutServlet</servlet-name>      </filter-mapping>      <servlet>        <servlet-name>XMLOutServlet</servlet-name>        <servlet-class>filters.XMLOutServlet</servlet-class>      </servlet>    </web-app> 

Compile all of the classes, making sure you include jaxp.jar and xalan.jar in your classpath. Then we need to create the WAR. You should have the following file and directory structure:

    WEB-INF/            web.xml            classes/filters/                            XMLOutServlet                            SmartXSLFilter                            OutputCapturer    xsl/        stockquotes.xsl 

Then, create the WAR with the following command:

    jar cvf filters.war WEB-INF/* xsl/* 

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:

click to expand



 < Free Open Study > 



Professional Java Servlets 2.3
Professional Java Servlets 2.3
ISBN: 186100561X
EAN: 2147483647
Year: 2006
Pages: 130

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net