Every other month in this column we (Deepak Alur, John Crupi,
Dan Malks, and other architects from the Sun Java Center
(www.sun.com/service/sunps/jdc)
will discuss various topics from our
book, Core J2EE Patterns: Best Practices and Strategies (Alur, Crupi,
Malks, Prentice Hall/Sun Press 2001). These topics include the 15
J2EE patterns in our catalog, design strategies, bad practices,
refactorings, and pattern-driven design in J2EE technology.
Patterns are expert solutions - recurring designs that have
proven effective over time. This month's article will provide you
with a bit more detail on the subject.
When applying patterns to the presentation tier, we address
the following:
- Pre- and postprocessing of a request and/or response
- Request handling, navigation, and dispatch
- View preparation and creation
- Application partitioning
In other words, how do we handle a Web request, managing any
necessary data access and generating presentation content for the
user? How might we transform the incoming or outgoing stream? How do
we determine what processing to perform to fulfill this request? What
view should be used to service the request and how do we generate it?
What are the logical abstractions and how do we design and decompose
our software to take advantage of these abstractions?
Six presentation tier patterns are documented in the Core
J2EE Patterns Catalog. The patterns and their tier categorizations
are shown in Table 1. Because of the focus of this column, we'll
describe just one of the presentation patterns in detail. First I
want to provide some context on how the presentation tier patterns
fit within a common J2EE architecture, starting with a basic
description of a system.
When a client makes a Web request for a particular resource,
the request is processed, a view is generated, and a result is
returned to the client. To provide more detail, we can define several
logical components and subcomponents of a typical Web-based
architecture:
- Request handler:
- Pre- and postprocessing
- Request processing
- Command processing
- Navigation and dispatch:
- Navigation resolution
- Request dispatching
- View processor:
- View preparation
- View creation
These components and subcomponents are shown visually in
Figure 1. Let's have a look at the Intercepting Filter pattern,
which addresses issues in the request-handling portion of the
architecture, as outlined above and in the figure.
Figure 1:
Intercepting Filter
The Intercepting Filter pattern documents issues relating to
preprocessing and postprocessing a Web request. Here are some common
examples of preprocessing:
- Decrypting an input stream: The incoming data may have been
encrypted for security purposes.
- Decompressing a stream: The incoming stream may have been
compressed for more efficient transfer over the network.
- Translating various encoding schemes to a common format:
Multiple encoding schemes require different handling. If each
encoding is translated to a common format, the core request-handling
mechanism can treat every request similarly. Some common examples of
encoding schemes are:
- application/x-www-form-urlencoded
- multipart/form-data
- Performing authentication or authorization: Authentication
and authorization may be performed either in a filter or as part of
the core processing flow, typically as part of the controller.
What constitutes postprocessing of a request? Here are some
common examples:
- Encrypting an output stream: The outgoing data may be
encrypted for security purposes.
- Compressing a stream: The outgoing stream may be compressed
for more efficient transfer over the network.
- Transformation of data for different clients: Transformation
into HTML, XML, or WML.
Additionally, one of the forces that motivates us to consider
this pattern is the desire to add and remove these processing
components independently - independent of other filtering components
and of the underlying core processing that fulfills the client's
request.
Let's look at the class diagram (see Figure 2) that describes
this pattern's structure. In the figure the actual resource that is
the target of the client request, such as a servlet or JavaServer
Page technology, is represented by the Target class. The individual
pre- or postprocessing components that perform filtering
functionality, as described above, are shown as Filter One, Filter
Two, and Filter Three. One important thing to notice in this diagram
is that there's no direct association between any of the filters and
the target resource. Additionally, there's no direct association from
any filter to one of the other filters.
Figure 2:
This is an important point, since it clarifies that the
filters are loosely coupled both to the target resource and to other
filters. This allows the filters to be easily added and removed
unobtrusively, as mentioned.
Filters are an excellent way to layer functionality onto your
system, providing pluggable behavior that can be used to decorate
core request processing. Another benefit of using this pattern is
that it promotes the reuse of these various filtering components
across different requests, in different combinations, and even in
different applications.
There are several implementation strategies for this pattern,
the most powerful of which leverages the standard filtering supported
in the Servlet specification 2.3. Vendor support for this revision of
the specification will be widespread in the not too distant future.
Listings 1 and 2 are excerpts from the Standard Filter
Strategy code example in the book. The example describes using
filters to preprocess requests, checking their encoding schemes, and
translating these different schemes to a common format. The common
format is to store all request states within request attributes.
Subsequently, any control code that checks for incoming values
will get these values from request attributes,
regardless of the original encoding.
Figure 3 is the sequence diagram that shows the basic
collaboration of the objects in the example. Note that in this
implementation the role of the FilterManager from the class diagram
is fulfilled by the Container in the sequence diagram. We hope this
provides you with a basic understanding of the benefits and some
implementing options for the Intercepting Filter pattern.
Figure 3:
Refactoring
There's more than one way to approach any task. This is as
true with software development as with anything else. So when I tell
you that people approach the task of developing software in different
ways, you certainly won't be surprised. Some folks feel that most
design work should precede implementation, while others like to jump
in, write some code, and start to think about how these bits of
implementation fit together. The difference is basically that of
top-down design versus bottom-up design.
Refactoring applies to either approach, though it's typically
applied in an environment where there is an understanding that design
is spread across the life of the project. That said, the fact is that
wherever there is coding, there may be refactoring. Martin Fowler, in
his great book Refactoring: Improving the Design of Existing Code
(Addison-Wesley), describes refactoring as "improving the design of
the code after it has been written." His book identifies many common
design flaws and describes the incremental coding changes that result
in improved design. The issues are typically general and not specific
to any particular area of Java or software development.
The lion's share of Fowler's book is devoted to what he calls
"small refactorings," meaning the design changes are at a very low
level, each involving several discrete coding modifications, such as
adding a parameter to a method. A small portion of the book,
coauthored by Kent Beck, is devoted to "big refactorings," which
exist at a higher level of abstraction and have steps that aren't as
well defined or as concrete.
In our book we include some J2EE technology-specific
refactorings, describing opportunities to improve
the design of a J2EE technology-based
system and the relevant steps involved. The format and style is based
on that in Fowler's book, which we find extremely valuable. Based on
Fowler and Beck's definition, the J2EE refactorings included in our
book might be called "medium refactorings" based on their level of
abstraction. The refactorings are listed in Table 2, categorized by
tier.
We find these refactorings to be excellent companions to the
patterns and bad practices described in the rest of our book. In
fact, you can think about the refactorings as often providing the
steps that help guide the developer from a less optimal solution, or
bad practice, to a more optimal one, suggested by a pattern.
In a future article we'll provide more information on these
refactorings and their relationship to the patterns in the catalog.
We'll also go into greater detail on the presentation, business, and
integration tiers, as well as communication across these tiers.
Thank you for reading, and please e-mail us at
CoreJ2EEPatterns@sun.com to provide feedback on this article and to
suggest other topics of interest.
Portions of this article contain excerpts with permission from Core
J2EE Patterns by Deepak Alur, John Crupi, and Dan Malks (ISBN
0-13-064884-1) Copyright 2001. Sun Microsystems, Inc.
Copyright 2001 Sun Microsystems, Inc. All Rights Reserved. Sun, Sun
Microsystems, the Sun logo, Java, J2EE, Java Center, and JavaServer
Pages are trademarks or registered trademarks of Sun Microsystems,
Inc., in the United States and other countries. Sun Microsystems,
Inc., may have intellectual property rights relating to
implementations of the technology described in this article, and no
license of any kind is given here. Please visit
www.sun.com/software/communitysource/
for licensing information.
The information in this article (the "information") is
provided "as is," for discussion purposes only. All express or
implied conditions, representations, and warranties, including any
implied warranty of merchantability, fitness for a particular
purpose, or non-infringement, are disclaimed, except to the extent
that such disclaimers are held to be legally invalid. Neither Sun nor
the authors make any representations, warranties, or guaranties as to
the quality, suitability, truth, accuracy, or completeness of the
information. Neither Sun nor the authors shall be liable for any
damages suffered as a result of using, modifying, contributing,
copying, or distributing the information.
Author Bio
Dan Malks, a senior Java architect with Sun Microsystems, is
currently focusing on distributed, service-based designs,
patterns, and implementations. He has developed in a variety of
environments, including Smalltalk and Java, while focusing on OO
technologies. Dan has published articles on Java in leading industry
periodicals, and holds bachelor's and master's degrees in computer
science. dan.malks@sun.com
Listing 1: Intercpting Filter Implementation Example:
Standard Encode Filter
public class StandardEncodeFilter
extends BaseEncodeFilter {
// Creates new StandardEncodeFilter
public StandardEncodeFilter() {}
public void doFilter(javax.servlet.ServletRequest
servletRequest,javax.servlet.ServletResponse
servletResponse,javax.servlet.FilterChain
filterChain)
throws java.io.IOException, javax.servlet.ServletException {
String contentType =
servletRequest.getContentType();
if ((contentType == null) ||
contentType.equalsIgnoreCase("application/x-www-form-urlencoded")) {
translateParamsToAttributes(servletRequest, servletResponse);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private void translateParamsToAttributes(ServletRequest request,
ServletResponse response)
{
Enumeration paramNames =
request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = (String)
paramNames.nextElement();
String [] values;
values = request.getParameterValues(paramName);
System.err.println("paramName = " + paramName);
if (values.length == 1)
request.setAttribute(paramName, values[0]);
else
request.setAttribute(paramName, values);
}
}
}
Listing 2: Intercepting Filter Implementaion Example:
MultipartEncoderFilter
public class MultipartEncodeFilter extends
BaseEncodeFilter {
public MultipartEncodeFilter() {}
public void doFilter(javax.servlet.ServletRequest
servletRequest, javax.servlet.ServletResponse
servletResponse,javax.servlet.FilterChain
filterChain)
throws java.io.IOException,
javax.servlet.ServletException {
String contentType =
servletRequest.getContentType();
// Only filter this request if it is multipart
// encoding
if (contentType.startsWith("multipart/form-data")){
try {
String uploadFolder =
getFilterConfig().getInitParameter("UploadFolder");
if (uploadFolder == null) uploadFolder = ".";
/** The MultipartRequest class is:
* Copyright (C) 2001 by Jason Hunter
* <jhunter@servlets.com>. All rights reserved.
**/
MultipartRequest multi = new
MultipartRequest(servletRequest,
uploadFolder,
1 * 1024 * 1024 );
Enumeration params =
multi.getParameterNames();
while (params.hasMoreElements()) {
String name = (String)params.nextElement();
String value = multi.getParameter(name);
servletRequest.setAttribute(name, value);
}
Enumeration files = multi.getFileNames();
while (files.hasMoreElements()) {
String name = (String)files.nextElement();
String filename =
multi.getFilesystemName(name);
String type = multi.getContentType(name);
File f = multi.getFile(name);
// At this point, do something with the
// file, as necessary
}
}
catch (IOException e)
{
LogManager.logMessage("error reading or saving file"+ e);
}
} // end if
filterChain.doFilter(servletRequest,
servletResponse);
} // end method doFilter()
}