Filters and Wrappers

Filters can intercept the request and also control the response all without the servlet knowing, which means that filters can be used without touching the servlets code, filters can be a very powerful tool.

Filters are Java components (very similar to servlets) that you can use to intercept and process requests before that are sent to the servlet, or to process responses after the servlet has completed, but before the response goes back to the client. The Container decides when to invoke your filters based on the declarations in the DD, the DD maps which filters will be called for which request URL patterns.

The are many things that you can do with filters

Filters can also be chained together, basically running one after the other, again the DD controls the order in which they run. You can quickly add or remove filters via the DD.

Filters have their own API, when a Java class implements the filter interface, it goes from a plain old class to an official J2EE Filter, this means you will have access to the ServletContext and be linked to other filters. Filters have a lifecycle, they have init() and destroy() methods and they have a doFilter() method. All your application filters will be declared in the DD and will specify if they are for a request or response and the order which they will be run.

Request Filter

A Request filter would look something like below, the comments explain what is going on within the code.

request filter

package com.example.web;

// The filter and FilterChain are in javax.servlet
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;

// You must implement the Filter interface
public class FilterTest implements Filter {

  private FilterConfig fc;

  // you must implement init(), you just save the config Object
  public void init(FilterConfig config) throws ServletException {
    this.fc = config;
  }

  // Notice we use the servlet request and resposne not the HTTP
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
  
    // now we cast the servlet response and request to the HTTP equivalent
    HttpServletRequest httpReq = (HttpServletRequest) req;

    // This may return null if the user did not authenticate, hence the else statement
    String name = httpReq.getRemoteUser();

    if (name != null) {
      fc.getServletContext().log("User " + name + " is updating");
    } else {
      fc.getServletContext().log("Unable to get username ");
    }

    // Now call the next filter or servlet or JSP
    chain.doFilter(req, resp);
  }

  // you must implement destroy but it is generally empty
  public void destory() {
    // do cleanup stuff
  }
}

When setting up a filter in the DD you need to do three things

All filters with matching URL patterns are located first, they are placed in the chain in the order in which they are declared in the DD. Once all filters with matching URLs are placed in the chain, the Container does the same thing with filters that have a matching <servlet-name> in the DD. Tomcat logged the information in the file localhost.log file but you can use the parameters passed to create a new log file.

declaring and ordering filters <web-app>
  <filter>
    <filter-name>FilterRequest</filter-name>
    <filter-class>foo.FilterTest</filter-class>
    <init-param>
      <param-name>LogFileName</param-name>
      <param-value>UserLog.txt</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>FilterRequest</filter-name>
    <url-pattern>/testfilter.jsp</url-pattern>
  </filter-mapping>
<web-app>

There are a number of rules that must be followed

New in version 2.4 (currently version is 2.5) filters can be applied to requests and responses from a forward() call, or include() call, request dispatch and/or the error handler. The new <dispatcher> element is used

request dispatcher filter <filter-mapping>
  <filter-name>MonitorFilter</filter-name>
  <url-pattern>*.do</url-pattern>
  <dispatcher>REQUEST</dispatcher
     - and / or -
  <dispatcher>INCLUDE</dispatcher
     - and / or -
  <dispatcher>FORWARD</dispatcher
     - and / or -
  <dispatcher>ERROR</dispatcher
</filter-mapping>

Response Filter

Response filters let us do something to the response output after the servlet has done it's thing and before the response is sent back to the client, however they are more complex to implement. A problem with changing the response is that the servlet output goes straight to the client and not back through the filter. You could implement your own response by making our own custom implementation of the HttpServletResponse interface and pass that to the servlet via the chain.doFilter() call, that custom implementation has to also include a custom output stream as well since that's the goal to capture the output after the servlet writes to it but before it goes back to the client.

Creating your own custom HttpServletResponse would be a real pain, this is where wrapper classes in the servlet API can help, they implement all the methods needed for the thing you are trying to wrap, delegating all calls to the underlying request or response object. All you need to do is extend one of the wrappers and overwrite just the methods you need to do your custom work. Sun created four convenience classes for this purpose

A wrapper wraps one kind of an object with an enhanced implementation, and by enhanced we mean add new capabilities while still doing everything the original wrapped thing did, basically it does everything it does and more, in non-J2EE it would be called a Decorator pattern.

Now for an example, which could be based on compressing the output stream to reduce network bandwidth, the code will be in two parts one for the wrapper and one for the filter

filter class

package com.example.web;

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

public class CompressionFilter implments Filter {

  private ServletContext ctx;
  private FilterConfig cfg;

  // save the config object and a quick ref to the servlet context object
  public void init(FilterConfig cfg) throws ServletException {
    this.cfg = cfg;
    ctx = cfg.getServletContext();
    ctx.log(cfg.getFilterName() + " initialized");
  }

  // wrap the response object with a Decorator that wraps the output stream with a compression I/O stream
  // Compression of the output stream is performed if and only if the client includes an Accept-Encoding Header
  public void doFilter(ServerRequest req, ServletResponse resp, FilterChain fc) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;

    // Does the client accept GZIP compression
    String valid_encodings = request.getHeader("Accept-Encoding");
    if ( valid_encodings.indexOf("gzip") > -1 ) {
      // If so wrap the response object with a compression wrapper
      CompressionRequestWrapper wrappedResp = new CompressionResponseWrapper(response);

      wrappedResp.setHeader("Context-Encoding", "gzip");
      fc.doFilter(request, wrappedResp);

      // a GZIP must be finished wioth which also flushes the GZIP stream buffer and sends all of its data
      // to the original response stream
      GZIPOutputStream gzos = wrappedResp.getGZIPOutputStream();
      gzos.finish();

      ctx.log(cfg.getFilterName() + ": finished the request");
    } else {
      ctx.log(cfg.getFilterName() + ": no encoding performed");
      fc.doFilter(request, response):
    }

    public void destroy() {
      // nulling out instance variables
      cfg = null:
      ctx = null;
    }
  }
}
    

wrapper class package com.example.web;

import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;
import java.utiol.zip.GZIPOutputStream;

class CompresssionResponseWrapper extends HttpServletResponserapper {

  // The compressed output stream for the servlet response
  private GZIPServletOutputStream servletGzipOS = null;
  
  // PrinteWriter object to the compressed output stream
  private Printerriter pw = null;

  CompressionResponseWrapper(HttpServletResponse resp) {
    super(resp);
  }

  public void setContentLength(int len) {}

  // A decorator method used by the filter, gives the compression filter a handle on the ZIP output
  // stream so that the filter can "finish" and flush the GZIP stream
  public GZIPOutputStream getGZIPOutputStream() {
    return this.servletGzipOS.internalGzipOS;
  }

  private object streamUsed = null;

  // provide access to a decorator servlet output stream
  public ServletOutputStream getOutputStream() throws IOException {

    // allow the servlet to access a servlet output stream only if the servlet has
    // not already accessed the print writer
    if ((streamUsed = null) && (streamUsed != pw)) {
      throw new IllegalStatexception();
    }

    // wrap the original servlet output stream with our compression servlet output stream
    if ( servletGzipOS == null ) {
      servletGzipOS = new GZIPOutputStream(getResponse().getOutputStream());
      streamUsed = servletGzipOS;
    }
    return servletGzipOS;
  }

  // Provide access to a decorated printer writer
  public PrinterWriter getWriter() throws IOException {

    if ((streamUsed != null) && (streamUsed != servletGzipOS)) {
      throw new IllegalStateException();
    }

    if (pw == null) {
      servletGzipOS = new GZIPServletOutputStream(getResponse().getOutputStream());
      OutputStreamWriter osw = new OutputStreamWriter(servletGzipOS, getResponse().getCharacterEncoding());
      pw = new PrinterWriter(osw);
      streamUsed = pw;
    }
    return pw;
  }
}
helper class class GIPServletutputStream extends ervletOutputStream {
  
  // get a ref to the RAW GZIP stream
  GZIPOutputStream internalGzipOS;

  // Decorator constructor
  GZIPServletOutputStream (ServletOutputStream sos) throws IOException {
    this.internalGzipOS = new GIPOutputStream(sos);
  }

  // implement the compression decorator by delegating the write() call to the GZIP compression stream
  // which is wrapping the original ServletOutputStream
  public void write(int param) throws java.io.IOException {
    internalGzipOS.write(param);
  }
}