Last month, we saw the power of Servlet filters to pre-process and
post-process the request/response flow in Java EE Web applications. Introduced
in Servlet API v2.3, filters are pluggable Web components that allow us to
intercept client request. While filters scale well to intercept incoming HTTP
request, intercepting the HTTP response can be problematic once the response is
flushed from a JSP view. In this article, we'll introduce another design pattern
called Decorator, which allows us to design custom response objects.
Decorator pattern
Decorator, a structural design pattern, helps to add behavior or
responsibilities to an existing object. The decorator pattern works by wrapping
the new 'decorator' object around the original object, which is typically
achieved by passing the original object as a parameter to the constructor of the
decorator, with the decorator implementing the new functionality.
|
Whenever a client requests for a servlet/JSP file, the servlet container
creates two objects-HttpServletRequest and HttpServletResponse. These objects
are then passed to the doGet() or doPost() method, depending on the request
method. In this article, we'll decorate the HTTP Response object and route the
same to the client. The following figure depicts a wrapped version of the
container-generated HttpServletResponse type.
Design Response Wrappers
With wrappers, you can change the behavior or extend functionality of a class by
extending it. Wrapper classes in the Servlet API provide a customized
implementation of request and response interfaces. All you need to do for
implementing your own customized request or response objects is just to extend
from any of the following four wrapper classes:
|
-
ServletRequestWrapper
-
HttpServletRequestWrapper
-
ServletResponseWrapper
-
HttpServletResponseWrapper
The wrapper classes provide convenient implementations of the
HttpServletRequest and HttpServletResponse interfaces that can be sub-classed by
developers wishing to adapt the request and response from a Servlet. This
relieves you from the pain of providing blank implementations for unwanted
methods. Prior to wrapper classes, the only way to customize the request and
response objects was to provide implementations for each method of
ServletRequest and/or ServletResponse interface. Here, we'll implement our own
response object and send the same back to the client, as shown in the diagram on
the next page.
The filter at the end of the request processing chain has access to the
customized response object and can call the additional methods defined by the
wrapper class. This gives the filter an opportunity to perform any post
processing logic (such as compression) on the response object.
|
Filtering HTTP response
We'll leverage from the built-in wrapper classes to design our own customized
response wrapper. To improve performance and response time of our Web
application, we'll design a compression filter, which will zip the flushed
output from JSP pages before sending it to the clients. The first step in this
direction is to define a response wrapper class, as shown in the following code
fragment.
package com.pcquest.medtracker.compression;
import java.util.zip.GZIPOutputStream;
//other necessary imports
class PCQCompressionResponseWrapper extends
HttpServletResponseWrapper {
//helper class for the servlet response
private GZIPServletOutputStream servletGzipOS = null;
private PrintWriter pw = null;
private Object streamUsed = null;
CompressionResponseWrapper(HttpServletResponse resp) {
super(resp);
}
// Override HttpServletResponse methods to
use our stream objects
public ServletOutputStream getOutputStream()
throws IOException {
// Allow the servlet to access a servlet output stream (SOS)
// only if the servlet has not already accessed the print writer.
if ( (streamUsed != null) && (streamUsed != servletGzipOS) ) {
throw new IllegalStateException();
}
// Wrap the original servlet output stream with our compression SOS.
if ( servletGzipOS == null ) {
servletGzipOS
= new GZIPServletOutputStream(getResponse()
.getOutputStream());
streamUsed = servletGzipOS;
}
return servletGzipOS;
}
//similarly implement the getWriter() method
...
}
The next step is to pass the wrapper as an argument to the
FilterChaindo.Filter() method for further processing. The following code
fragment defines a filter implementation class, which passes out own custom
designed HTTP response object to the next entity in the request processing
chain. For the complete source code, watch out for the developer thread on
forums.pcquest. com.
package com.pcquest.medtracker.compression;
import java.util.zip.GZIPOutputStream;
//other necessary imports
class PCQCompressionFilter implements Filter {
//override init()
public void doFilter(ServletRequest req,
ServletResponse resp,
FilterChain fc)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// Dose the client accept GZIP compression?
String valid_encodings = request.getHeader("Accept-Encoding");
if ( (valid_encodings != null) && (valid_encodings.indexOf("gzip") > -1) ) {
// Then wrap the response object with a compression wrapper
PCQCompressionResponseWrapper wrappedResp
= new PCQCompressionResponseWrapper(response);
// Declare that the response content is being GZIP
encoded.
wrappedResp.setHeader("Content-Encoding", "gzip");
// Chain to the next component (thus processing the
request)
fc.doFilter(request, wrappedResp);
// A GZIP compression stream must be "finished"
GZIPOutputStream gzos = wrappedResp.getGZIPOutputStream();
gzos.finish();
// The container handles the rest of the work.
ctx.log(cfg.getFilterName() + ": finished the request.");
} else {
fc.doFilter(request, response);
ctx.log(cfg.getFilterName() + ": no encoding performed.");
}
}
//override destroy() method
}
Rolling Compression Filter
After wrapping a customized response around the standard HTTP response object,
you need to configure the deployment descriptor file for your customized wrapper
filter to start intercepting outgoing response. The Web deployment plan is shown
here.
<filter>
>
After configuring the Web application, any request for URI matching
>/compress/*.jsp pattern will run through the compression filter.
Conclusion
The Decorator design pattern provides a scalable and easy customizable solution
to common business problems. Decorator adds power to simple inheritance through
delegation and run-time dispatching. Decorator is much powerful than simple
subclassing. With subclassing, you work with the class, whereas in the Decorator
pattern you modify objects dynamically. When you extend a class, the change you
make to the subclass will affect all instances of the child class. With the
Decorator pattern, though, you apply changes to each individual object you want
to.