Previous | Next
Servlet FiltersVersion 2.3 of the Java servlet specification adds a new feature called filters. A filter is an object that intercepts requests to a servlet, JSP, or static file in a web application. The filter has the opportunity to modify the request before passing it along to the underlying resource and can capture and modify the response before sending it back to the client. Since filters can be specified declaratively using the web application deployment descriptor, they can be inserted into existing web applications without altering any of the existing code. Filter OverviewServlet filters are useful for many purposes, including logging, user authentication, data compression, encryption, and XSLT transformation. Many filters can be chained together, each performing one specific task. For the purposes of this tutorial, XSLT transformations are the most interesting use of filters. Figure 8-5 illustrates the general filter architecture. Figure 8-5. Servlet filters
void init(FilterConfig config) void destroy( ) void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) The The chain.doFilter(req, res); Although the next entry in the chain might be another filter, it is probably a servlet or a JSP. Either way, the filter does not have to know this. Simply calling To facilitate this capability, Version 2.3 of the servlet API also adds wrapper classes that allow the request and response to be modified. The following new classes are now available:
Each of these classes merely wraps around another request or response, and all methods merely delegate to the wrapped object. To modify behavior, developers must extend from one of these classes and override one or more methods. Here is how a custom filter might look: public class MyFilter implements Filter { public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // wrap around the original request and response MyRequestWrapper reqWrap = new MyRequestWrapper(req); MyResponseWrapper resWrap = new MyResponseWrapper(res); // pass the wrappers on to the next entry chain.doFilter(reqWrap, resWrap); } } In this case, public interface ServletResponse { ServletOutputStream getOutputStream( ) throws IOException; ...additional methods } Here is how public class ServletResponseWrapper implements ServletResponse { private ServletResponse response; public ServletResponseWrapper(ServletResponse response) { this.response = response; } To modify the response sent to the client browser, the custom wrapper subclass must override the public class MyResponseWrapper extends ServletResponseWrapper {
XSLT Transformation FilterThe previous discussion introduced a lot of concepts about servlet filters without a lot of details. Next, a complete example for performing XSLT transformations using a filter is presented. Hopefully this will illustrate some of the issues mentioned so far. The basic goal is to create a servlet filter that performs XSLT transformations. A servlet, JSP, or static XML file will provide the raw XML data. The filter will intercept this XML before it is sent to the client browser and apply an XSLT transformation. The result tree is then sent back to the browser. Example 8-9 is the first of three classes that comprise this example. This is a custom subclass of Example 8-9. BufferedServletOutputStream.javapackage com.anonymous.javaxslt.util; import java.io.*; import javax.servlet.*; /** * A custom servlet output stream that stores its data in a buffer, * rather than sending it directly to the client. * * @author Eric M. Burke */ The Example 8-10. BufferedHttpResponseWrapper.javapackage com.anonymous.javaxslt.util; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; /** * A custom response wrapper that captures all output in a buffer. */ public class BufferedHttpResponseWrapper extends HttpServletResponseWrapper { private BufferedServletOutputStream bufferedServletOut = new BufferedServletOutputStream( ); private PrintWriter printWriter = null; private ServletOutputStream outputStream = null; public BufferedHttpResponseWrapper(HttpServletResponse origResponse) { super(origResponse); } public byte[] getBuffer( ) { return this.bufferedServletOut.getBuffer( ); } public PrintWriter getWriter( ) throws IOException { if (this.outputStream != null) { throw new IllegalStateException( "The Servlet API forbids calling getWriter( ) after" + " getOutputStream( ) has been called"); } if (this.printWriter == null) { this.printWriter = new PrintWriter(this.bufferedServletOut); } return this.printWriter; } public ServletOutputStream getOutputStream( ) throws IOException { if (this.printWriter != null) { throw new IllegalStateException( "The Servlet API forbids calling getOutputStream( ) after" + " getWriter( ) has been called"); } if (this.outputStream == null) { this.outputStream = this.bufferedServletOut; } return this.outputStream; } // override methods that deal with the response buffer public void flushBuffer( ) throws IOException { if (this.outputStream != null) { this.outputStream.flush( ); } else if (this.printWriter != null) { this.printWriter.flush( ); } } public int getBufferSize( ) { return this.bufferedServletOut.getBuffer( ).length; } public void reset( ) { this.bufferedServletOut.reset( ); } public void resetBuffer( ) { this.bufferedServletOut.reset( ); } public void setBufferSize(int size) { this.bufferedServletOut.setBufferSize(size); } }
According to the servlet API, either WARNING: Very little of this is currently documented in the servlet specification. Perhaps this will improve by the time this tutorial is published. However, there are currently very few examples that show how to capture and modify the response. Hopefully this will improve as more containers are upgraded to support the servlet 2.3 specification. The primary class in this example is shown in Example 8-11. This is the actual filter that performs XSLT transformations. Example 8-11. Servlet filter for XSLT transformationspackage com.anonymous.javaxslt.util; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; /** * A utility class that uses the Servlet 2.3 Filtering API to apply * an XSLT stylesheet to a servlet response. * * @author Eric M. Burke */ This filter requires the deployment descriptor to provide the name of the XSLT stylesheet as an initialization parameter. The following line of code retrieves the parameter: String xsltPath = filterConfig.getInitParameter("xsltPath"); The if (!(res instanceof HttpServletResponse)) { throw new ServletException("This filter only supports HTTP"); } Since there is no HTTP-specific filter interface, custom filters must use Next, the filter creates the buffered response wrapper and delegates to the next entry in the chain: BufferedHttpResponseWrapper responseWrapper = new BufferedHttpResponseWrapper((HttpServletResponse) res); chain.doFilter(req, responseWrapper); This effectively captures the XML output from the chain, making the XSLT transformation possible. Before doing the transformation, however, one "hack" is required to work with Tomcat 4.0: byte[] origXML = responseWrapper.getBuffer( ); if (origXML == null || origXML.length == 0) { // just let Tomcat deliver its cached data back to the client chain.doFilter(req, res); return; } The complete explanation is captured in the source code comments in Example 8-11. Basically, Tomcat seems to cache its response when the user tries to reload the same static file consecutive times. Without this check, the code fails because the
Finally, the filter uses JAXP to perform the XSLT transformation, sending the result tree to the original servlet response. The deployment descriptor is listed in Example 8-12. Example 8-12. Filter deployment descriptor<?xml version="1.0" encoding="ISO-8859-1"?> As the first few lines of the deployment descriptor indicate, filters require Version 2.3 of the web application DTD. The filter initialization parameter is specified next, inside of the Finally, the deployment descriptor lists several explicit mappings for this filter. In the examples shown, the filter is applied to static XML files. It can just as easily be applied to a servlet or JSP, however. Closing Thoughts on FiltersUsing filters for XSLT transformations is an interesting concept, primarily because it allows different stylesheets to be applied to XML from many different sources using the web application deployment descriptor. To use a different stylesheet, merely change the deployment descriptor. One interesting approach is using JSP to generate pure XML, then applying a filter to transform that XML into XHTML for the client. Filters do suffer drawbacks and probably are not the best solution for most applications. First and foremost, the filter API is available only in Version 2.3 of the servlet specification; many existing servlet containers do not support filters at all. In the case of XSLT transformations, a custom Finally, this approach is slower than others. The XML must be converted into text and buffered in memory before the XSLT transformation can be performed, which is generally slower than sending SAX events or a DOM tree directly to the XSLT processor. Generating XML and performing the XSLT transformation in a servlet can avoid the extra conversions to and from text that filters require. |