Return-Path: <env_3293992085908251857@hermes.java.sun.com>
Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id CAA02390; Wed, 27 Jun 2001 02:31:41 -0400 (EDT)
Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85])
	by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id CAA11542
	for <alexp@mit.edu>; Wed, 27 Jun 2001 02:31:40 -0400 (EDT)
Message-Id: <200106270631.CAA11542@pacific-carrier-annex.mit.edu>
Date: Tue, 26 Jun 2001 23:31:40 PDT
From: "JDC Tech Tips" <body_3293992085908251857@hermes.java.sun.com>
To: alexp@mit.edu
Subject: JDC Tech Tips  June 26, 2001
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
X-Mailer: Beyond Email 2.2

 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
June 26, 2001. This issue covers two tips on servlet filters:

         * Improving Code Reuse With Servlet Filters
         * Using Filters To Modify The Server's Response
         
In order to run the code in these tips you need access to
a servlet engine that is compatible with the Java(tm) Servlet 2.3
Specification, now in public final draft 2. The instructions
assume you are using the reference implementation, Tomcat 4.0 
beta 5 or later. You can download Tomcat at 
http://jakarta.apache.org/tomcat/index.html.         
                 
These tips were developed using Java 2 SDK, Standard Edition, 
v 1.3.

This issue of the JDC Tech Tips is written by Stuart Halloway,
a Java specialist at DevelopMentor (http://www.develop.com/java).

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2001/tt0626.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
IMPROVING CODE REUSE WITH SERVLET FILTERS

There are several ways to reuse code in a servlet environment. 
For example, you might use inheritance to reuse the code in the
YourCoolServlet class from a new, improved YourCoolerServlet. 
Also, the Java Servlet specification defines natural mechanisms 
to chain execution from one servlet to another. You can use the 
java.servlet.RequestDispatcher's forward or include methods to 
transfer control from one servlet to another.

While all of these techniques have their use, they also suffer
from some disadvantages. They require you to explicitly wire
servlets together in code, which means that you must plan for
reuse during development. With the forward and include methods, 
you cede too much control to the downstream servlet -- you might 
not be able to modify the content that a downstream servlet 
writes into the response.

With the Java Servlet 2.3 API, you now have another option: use
servlet filters. Servlet filters are a new feature in the Java 
Servlet 2.3 API. They allow you to transform the content of
requests and responses. Each resource provided by the servlet
engine can be hidden behind an arbitrary chain of filters. As a
request passes through, each filter gets a chance to view, and
optionally modify, the behavior of the underlying resource. Best 
of all, setting up a chain of servlet filters is an 
administrative task that requires no changes to existing code.

Consider a simple use of filters where an administrator has 
installed two filters in front of some resource. Each filter can 
analyze the request, and decide whether to modify the request 
before passing it to the next object in the "filter chain." Also, 
each filter can modify the response to be sent back to the 
client. The filter can do this either before or after calling the 
next object in the chain. The resource at the end of the chain 
could be a servlet, JSP, static content, or any other end point 
that your engine provides.

To see the power of servlet filters, consider another scenario.
Imagine that you're working for RockSolidBids, an online bidding
system. You arrive at work one morning to discover that one of
your partners, FlakyNetworks Inc., is having network problems. 
Unfortunately, your own application contains reams of code that 
connects to FlakyNetworks. You do not have time to fix the 
problem in each individual servlet. The situation is getting 
critical -- the failures and retries attempting to connect to 
FlakyNetworks are hogging bandwidth and bringing down the rest of 
your customers. Your mission: add a filter in front of all of 
your servlets to temporarily reject all requests involving 
FlakyNetworks Inc.

With a servlet filter, you can easily solve this problem without
touching existing code. First, define a RequestBlocker class that 
implements the Filter interface:

package com.develop.filters;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class RequestBlocker implements Filter {
  private Properties blockThese = new Properties();
  {
    blockThese.setProperty("company", "FlakyNetworks");
  }
  private ServletContext ctx;
  public void init(javax.servlet.FilterConfig filterConfig) 
         throws ServletException {
    ctx = filterConfig.getServletContext();
    ctx.log("Filter " + filterConfig.getFilterName() + 
                                                " initialized.");
  }
  public void doFilter(ServletRequest servletRequest, 
                       ServletResponse servletResponse, 
                       FilterChain filterChain) 
              throws java.io.IOException, ServletException {
    HttpServletRequest hsr = (HttpServletRequest)servletRequest;
    for (Enumeration en = blockThese.keys(); 
                                        en.hasMoreElements();) {
      String name = (String)en.nextElement();
      String param = hsr.getParameter(name);
      if ((param != null) && blockThese.get(name).equals(param)) {
        HttpServletResponse hResp = 
                          (HttpServletResponse) servletResponse;
        String msg = "Request for " + name + " " 
                                         + param + " rejected.";
        hResp.sendError(hResp.SC_SERVICE_UNAVAILABLE, msg);
        ctx.log(msg);
        return;
      }
    }
    filterChain.doFilter(servletRequest, servletResponse);
  }
  public void destroy() {
  }
}

The Filter interfaces defines three methods:

o init. This method is called before the servlet engine begins 
  using the filter. In this example the init method simply 
  stores the ServletContext for use later and logs that it 
  completed successfully. 
  
o destroy. This method is called before the engine removes 
  a filter from service. If you need to clean up 
  filter-specific resources, you can do that with the destroy
  method.
  
o doFilter. This method is the meat of the filter. Inside this
  method, you have access to the request and response objects, 
  just as you would in a normal servlet's doGet or doPost method. 
  You can query or modify these objects as needed. Then, you can 
  forward the request to the next filter in the chain (or to the 
  servlet if this is deployed as the last filter) by calling
  filterChain.doFilter.

The RequestBlocker's implementation of doFilter is very simple. 
It loops over a set of key/value pairs defined by the blockThese
instance (that is, "company", "FlakyNetworks"). If any request 
parameters match the key/value pair, the filter uses the response 
object to send an HTTP 503 error (SC_SERVICE_UNAVAILABLE) back to 
the client. Because the filter has dealt completely with the 
request at this point, it does not call filterChain.doFilter. If 
the request does not contain any disallowed parameters, the 
RequestBlocker does nothing and passes the request to the next 
filter/servlet.

To use the RequestBlocker, you must deploy it into a servlet 
engine. The instructions that follow assume that you are
deploying into Tomcat's examples application. The instructions
for other engines should be similar.  

1. Compile the RequestBlocker with servlet.jar on your class path.
   Tomcat keeps servlet.jar at common/lib. Copy the 
   RequestBlocker.class file to
/{yourTomcat}/webapps/examples/WEB-INF/classes/com/develop/filters

2. Open the deployment descriptor
/{yourTomcat}/webapps/examples/WEB-INF/web.xml

3. Define the filter by adding this XML fragment at the bottom of 
   the list of filters elements:
   <filter>
     <filter-name>Request Blocker</filter-name>
     <filter-class>com.develop.filters.RequestBlocker</filter-class>
   </filter>
   This associates the name "Request Blocker" with your filter.

4. Tell the engine when to use the filter by adding this XML
   fragment to the bottom of the filter-mappings:
   <filter-mapping>
     <filter-name>Request Blocker</filter-name>
     <url-pattern>/*</url-pattern>
   </filter-mapping>
   This tells the engine to invoke the filter for all URLs that map
   to the application.

To start Tomcat, run bin\startup from your Tomcat root directory. 
After Tomcat starts, use the browser of your choice to navigate 
into the examples application. If you have not changed Tomcat's 
default port, you can use the URL
http://localhost:8080/examples/servlets/index.html.

The application should run normally. Try using various example 
servlets. Each of these accesses is filtered, but because none 
of the requests mentions FlakyNetworks, the filter is entirely 
transparent.

What happens if you edit a URL such that it mention FlakyNetworks? 
Try the following URL (all on one line):
  http://localhost:8080/examples/servlet/
  HelloWorldExample?company=FlakyNetworks
Now the filter takes action, and you should see browser
message 503, "Request for company FlakyNetworks rejected."

This simple filter provides an excellent example of code reuse. 
You are reusing all of the servlets in your system, and you have 
changed their behavior: they now reject calls to FlakyNetworks. 
However, this kind of reuse does not require inheritance or 
delegation to specific servlets. In fact, you do not even have to 
assume that there is a servlet at the end of the chain. The 
filter works just as well for any resource, even simple HTML 
pages. For example, try the HTML page 
http://localhost:8080/examples/?company=FlakyNetworks.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
USING FILTERS TO MODIFY THE SERVER'S RESPONSE

The RequestBlocker example in the tip "Improving Code Reuse With 
Servlet Filters" used filters to reject certain requests. A more
important use of filters is to modify the response sent back from 
the server. The XSLTFilter class shown below automatically
performs a transform on an XML document returned by a server,
rendering the document into HTML before returning it to the 
caller.

//class com.develop.filters.XSLTFilter
package com.develop.filters;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class XSLTFilter implements Filter {
  private ServletContext ctx;
  private String xslt;
  private TransformerFactory tf = TransformerFactory.newInstance();
  private Transformer xform;
  
  private static class ByteArrayServletStream extends 
                                        ServletOutputStream {
    ByteArrayOutputStream baos;
    ByteArrayServletStream(ByteArrayOutputStream baos) {
      this.baos = baos;
    }
    public void write(int param) throws java.io.IOException {
      baos.write(param);
    }
  }
  
  private static class ByteArrayPrintWriter {
    private ByteArrayOutputStream baos = 
                                   new ByteArrayOutputStream();
    private PrintWriter pw = new PrintWriter(baos);
    private ServletOutputStream sos = 
                              new ByteArrayServletStream(baos);
    
    public PrintWriter getWriter() {
      return pw;
    }
    public ServletOutputStream getStream() {
      return sos;
    }
    byte[] toByteArray() {
      return baos.toByteArray();
    }
  }

  public void init(FilterConfig filterConfig) throws 
                                             ServletException {
    ctx = filterConfig.getServletContext();
    xslt = filterConfig.getInitParameter("xslt");
    ctx.log("Filter " + filterConfig.getFilterName() + 
                                        " using xslt " + xslt);
    try {
      xform = tf.newTransformer(new StreamSource(
                               ctx.getResourceAsStream(xslt)));
    } catch (Exception e) {
      ctx.log("Could not intialize transform", e);
      throw new ServletException(
                          "Could not initialize transform", e);
    }
  }
  
  public String httpReqLine(HttpServletRequest req) {
    StringBuffer ret = req.getRequestURL();
    String query = req.getQueryString();
    if (query != null) {
      ret.append("?").append(query);
    }
    return ret.toString();
  }
  
  public String getHeaders(HttpServletRequest req) 
                                             throws IOException {
    Enumeration en = req.getHeaderNames();
    StringBuffer sb = new StringBuffer();
    while (en.hasMoreElements()) {
      String name = (String) en.nextElement();
      sb.append(name).append(": ").append(
                               req.getHeader(name)).append("\n");
    }
    return sb.toString();
  }
  public void doFilter(ServletRequest servletRequest, 
                       ServletResponse servletResponse, 
                       FilterChain filterChain) 
              throws java.io.IOException, ServletException {
    HttpServletRequest hsr = (HttpServletRequest)servletRequest;
    final HttpServletResponse resp = (
                             HttpServletResponse)servletResponse;
    ctx.log("Accessing filter for " + httpReqLine(hsr) + 
                                          " " + hsr.getMethod());
    
    final ByteArrayPrintWriter pw = new ByteArrayPrintWriter();
    final boolean[] xformNeeded = new boolean[1];
    HttpServletResponse wrappedResp = 
                           new HttpServletResponseWrapper(resp) {
      public PrintWriter getWriter() {
        return pw.getWriter();  
      }
      public ServletOutputStream getOutputStream() {
        return pw.getStream();  
      }
      public void setContentType(String type) {
        if (type.equals("text/xml")) {
          ctx.log("Converting xml to html");
          resp.setContentType("text/html");
          xformNeeded[0] = true;
        } else {
          resp.setContentType(type);
        }
      }
    };
    filterChain.doFilter(servletRequest, wrappedResp);
    byte[] bytes = pw.toByteArray();
    if (bytes == null || (bytes.length == 0)) {
      ctx.log("No content!");
    }
    if (xformNeeded[0] == true) {
      try {
        xform.transform(new StreamSource(
                                new ByteArrayInputStream(bytes)), 
                        new StreamResult(resp.getOutputStream()));
        ctx.log("XML -> HTML conversion completed");
      } catch (Exception e) {
        throw new ServletException(
                              "Unable to transform document", e);
      }
    } else {
      resp.getOutputStream().write(bytes);
    }
  }
  public void destroy() {
    ctx.log("Destroying filter...");
  }
}

The XSLTFilter class is a good deal more complex than the
RequestBlocker example, but the single critical method is 
doFilter. Instead of passing the response object to the next 
filter in the chain, XSLTFilter's call to doFilter specifies 
a customized response object named wrappedResp.

To make it easy to replace the response object, the filter
architecture provides a helper class, named 
HttpServletResponseWrapper, that wraps the original response 
object, and simply passes through every method call. XSLTFilter 
creates an anonymous subclass of HttpServletResponseWrapper, 
overriding three methods: getOutputStream, getWriter, and 
setContentType.

The getOutputStream and getWriter methods use an instance of the
nested helper class ByteArrayPrintWriter, named pw. When a 
downstream filter or servlet writes into the "response," it
actually writes into the instance of pw.

The setContentType method checks to see if the content being 
returned is "text/xml". If a downstream servlet or filter tries 
to set the content type to "text/xml", the overridden 
setContentType changes it to "text/html" instead, and sets 
a flag, xformNeeded[0], indicating that the transform needs to 
run.

After calling doFilter, XSLTFilter checks to see if it needs to
transform the response. If it does, it takes the downstream 
response from pw, and transforms it into the "real" response, 
resp. Of course, resp might not be the "real" response either, 
because XSLTFilter might be downstream from yet another filter.

The transform is performed using XSLT, which is loaded using the 
Java API for XML Parsing (JAXP) TransformerFactory class. 
Setting up the transform illustrates another filter feature: 
initialization parameters. The XSLTFilter expects to be 
configured with an initialization parameter named "xslt" that 
specifies which transform to run.

To see XSLTFilter in action, execute the following steps:

1. You will need an XSLT processor. This tip was tested with 
   the open source processor Xalan-Java version 2.1.0, which 
   you can download from 
   http://xml.apache.org/xalan-j/index.html.

2. Assuming you are working in the examples application of 
   Tomcat, add xerces.jar and xalan.jar to 
   {yourTomcat}/webapps/examples/lib

3. Compile XSLTFilter with servlet.jar and jaxp.jar on your 
   class path. Both these jar files can be found in the Tomcat 
   installation. Copy the XSLTFiler.class to
   {yourTomcat}/webapps/examples/WEB-INF/classes/com/develop/filters

4. Configure the filter. You will need to add two XML fragments to 
   {yourTomcat}/webapps/examples/WEB-INF/web.xml

  <filter>
    <filter-name>XSLT Filter</filter-name>
    <filter-class>com.develop.filters.XSLTFilter</filter-class>
    <init-param>
      <param-name>xslt</param-name>
      <!-- Change the param-value to the XSLT you want to use -->
      <param-value>/xform2.xsl</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>XSLT Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

5. Make sure that your servlet engine is configured to set the 
   content type for XML files. In Tomcat you will edit the 
   {yourTomcat}/conf/web.xml file and add the XML fragment
   <!-- add to the list of mime-mappings already present -->
   <mime-mapping>
     <extension>xml</extension>
     <mime-type>text/xml</mime-type>
   </mime-mapping>

6. Install some XML and XSL files. The configuration step above
   assumes that you are using xform2.xsl and Index.xml as shown
   below. Copy the files to
   {YourTomcat}/webapps/examples

<?xml version="1.0" encoding="iso-8859-1"?>
<!-- File Index.xml -->
<tips>
<author id="stu" fullName="Stuart Halloway"/>
<author id="glen" fullName="Glen McCluskey"/>
<tip title="Using the SAX API"
     author="stu"
     htmlURL="http://java.sun.com/jdc/TechTips/2000/tt0627.html#tip2"
     textURL="http://java.sun.com/jdc/TechTips/txtarchive/June00_Stu.txt">
</tip>
<tip title="Random Access for Files"
     author="glen"
     htmlURL="http://java.sun.com/jdc/TechTips/2000/tt0509.html#tip1"
     textURL="http://java.sun.com/jdc/TechTips/txtarchive/May00_GlenM.txt">
</tip>
</tips>

<!-- File Xform2.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="/">
  <HTML><BODY><H1>JDC Tech Tips Archive</H1>
  <xsl:apply-templates/>
  </BODY></HTML>
  </xsl:template>

  <!-- list the title of a tip -->
  <xsl:template match="tip">
  <br><xsl:apply-templates select="@*"/><xsl:value-of select="@title"/></br>
  </xsl:template>

  <!-- create a link to any htmlURL -->
  <xsl:template match="@htmlURL">
  <A HREF="{.}"> HTML </A> |
  </xsl:template>

  <!-- create a link to any textURL -->
  <xsl:template match="@textURL">
  <A HREF="{.}"> TEXT </A> |
  </xsl:template>

  <!-- ignore other attributes -->
  <xsl:template match="@*"/>
</xsl:stylesheet>

7. Restart Tomcat. Using your browser, navigate to 
   http://localhost:8080/examples/Index.xml. The filter will
   automatically transform this document into HTML.

8. Try other XML and XSL documents. Do not forget to restart 
   Tomcat if you change the init-param for the filter.  
 
To learn more about servlet filters, see the Java Servlet 2.3
Specification at 
http://java.sun.com/products/servlet/index.html

For an overview of new features in the Java Servlet 2.3 
Specification, see the article "Newer is Better" by Kevin Jones 
at 
http://www.java-pro.com/upload/free/Features/Javapro/2001/07jul01/kj0107/kj0107-1.asp

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

- NOTE

Sun respects your online time and privacy. The Java Developer 
Connection mailing lists are used for internal Sun Microsystems(tm) 
purposes only. You have received this email because you elected 
to subscribe. To unsubscribe, go to the Subscriptions page 
(http://developer.java.sun.com/subscription/), uncheck the 
appropriate checkbox, and click the Update button.

As of May  22, 2001, Sun Microsystems updated its Privacy Policy 
(http://sun.com/privacy) to give you a better understanding of 
Sun's Privacy Policy and Practice. If you have any questions, 
contact privacy@sun.com.


- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
choose the newsletters you want to subscribe to, and click Update.


- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com


- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html


- COPYRIGHT
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html


- LINKS TO NON-SUN SITES
The JDC Tech Tips may provide, or third parties may provide, 
links to other Internet sites or resources. Because Sun has no 
control over such sites and resources, You acknowledge and agree 
that Sun is not responsible for the availability of such external 
sites or resources, and does not endorse and is not responsible 
or liable for any Content, advertising, products, or other 
materials on or available from such sites or resources. Sun will 
not be responsible or liable, directly or indirectly, for any 
damage or loss caused or alleged to be caused by or in connection 
with use of or reliance on any such Content, goods or services 
available on or through any such site or resource.


JDC Tech Tips 
June 26, 2001

Sun, Sun Microsystems, Java, and Java Developer Connection are 
trademarks or registered trademarks of Sun Microsystems, Inc. 
in the United States and other countries.








	To use our one-click unsubscribe facility, select the following URL:
	http://hermes.java.sun.com/unsubscribe?3293992085908251857
