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.
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 // Notice we use the servlet request and resposne not the HTTP |
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 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.*; |
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); } } |