Commons Chain, as discussed in part one of this two-part series, provides a Java-based framework and API for representing sequential processes. Developed under the umbrella of the Jakarta Commons project, Commons Chain is being put to test in the latest development release of Struts (which I will refer to as Struts 1.3). In part two today, I'll cover how Struts leverages Chain to facilitate the processing of HTTP requests.
Commons Chain allows you to define sequential sets of command. Each set of
commands, a chain, is responsible for fulfilling some process. Each command in the
chain specifies a Java class that implements the Command interface. This interface
contains a single execute method that receives a Context instance. The execute method
returns true to indicate that the request was processed completely and the chain should
end. If false is returned, processing moves on to the next command in the chain.
You can define a chain using the Commons Chain API or via an XML file. Using an XML file provides the most flexibility, as you can change the chain definition at deployment time. Here's a simple chain configuration file taken from my first article:
<catalog>
<chain name="sell-vehicle">
<command id="GetCustomerInfo"
className="com.jadecove.chain.sample.GetCustomerInfo"/>
<command id="TestDriveVehicle"
className="com.jadecove.chain.sample.TestDriveVehicle"/>
<command id="NegotiateSale"
className="com.jadecove.chain.sample.NegotiateSale"/>
<command id="ArrangeFinancing"
className="com.jadecove.chain.sample.ArrangeFinancing"/>
<command id="CloseSale"
className="com.jadecove.chain.sample.CloseSale"/>
</chain>
</catalog>
Struts uses Chain to replace its traditional HTTP-request processing, which was handled
by the RequestProcessor class. The Struts ActionServlet determines the
RequestProcessor to use based on the struts-config.xml file. If not specified explicitly, the
ActionServlet uses org.apache.struts.action.RequestProcessor. It acquires
an instance of the RequestProcessor, calls its init method, and then its process
method.
Here's the init method of the RequestProcessor:
public void init(ActionServlet servlet,
ModuleConfig moduleConfig)
throws ServletException {
synchronized (actions) {
actions.clear();
}
this.servlet = servlet;
this.moduleConfig = moduleConfig;
}
Nothing magical here--the method simply clears its cache of Action instances and sets
some instance variables. The heart of the RequestProcessor lies in its process method.
This method defines a sequential algorithm for processing the request and response.
public void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Wrap multipart requests with a special wrapper
request = processMultipart(request);
// Identify the path component we will use to select a mapping
String path = processPath(request, response);
if (path == null) {
return;
}
// Select a Locale for the current user if requested
processLocale(request, response);
// Set the content type and no-caching headers if requested
processContent(request, response);
processNoCache(request, response);
// General purpose preprocessing hook
if (!processPreprocess(request, response)) {
return;
}
this.processCachedMessages(request, response);
// Identify the mapping for this request
ActionMapping mapping = processMapping(request, response, path);
if (mapping == null) {
return;
}
// Check for any role required to perform this action
if (!processRoles(request, response, mapping)) {
return;
}
// Process any ActionForm bean related to this request
ActionForm form = processActionForm(request, response, mapping);
processPopulate(request, response, form, mapping);
if (!processValidate(request, response, form, mapping)) {
return;
}
// Process a forward or include specified by this mapping
if (!processForward(request, response, mapping)) {
return;
}
if (!processInclude(request, response, mapping)) {
return;
}
// Create or acquire the Action instance to process this request
Action action = processActionCreate(request, response, mapping);
if (action == null) {
return;
}
// Call the Action instance itself
ActionForward forward =
processActionPerform(request, response,
action, form, mapping);
// Process the returned ActionForward instance
processForwardConfig(request, response, forward);
}
This process is tailor-made for Commons Chain. (It's no coincidence that some of the
Struts committers are also committers on Chain.) The process consists of a series of
steps, each represented by a processFoo method. The inputs primarily consist of the
request and response objects. Some of the methods return Struts objects like the
ActionMapping and ActionForm. If these objects are null, then false is returned,
indicating that the process cannot continue. Other methods directly return a true/false
value, with false indicating that the process should not continue and that the request has
been handled.
Struts 1.3 provides a new request processor class, ComposableRequestProcessor, that
extends the original RequestProcessor and overrides the init and process methods to
utilize Commons Chain. The init method of the ComposableRequestProcessor loads
the request processing chain from a chain catalog. By default, it looks under the catalog
named struts and looks for the command named servlet-standard.
public void init(ActionServlet servlet,
ModuleConfig moduleConfig)
throws ServletException {
super.init(servlet, moduleConfig);
ControllerConfig controllerConfig =
moduleConfig.getControllerConfig();
String catalogName = controllerConfig.getCatalog();
catalog = CatalogFactory.getInstance().getCatalog(catalogName);
if (catalog == null) {
throw new ServletException("Cannot find catalog '" +
catalogName + "'");
}
String commandName = controllerConfig.getCommand();
command = catalog.getCommand(commandName);
if (command == null) {
throw new ServletException("Cannot find command '" +
commandName + "'");
}
}
|
The ComposableRequestProcessor overrides the process method so that it can run the
chain (that is, execute the command):
public void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Wrap the request in the case of a multipart request
request = processMultipart(request);
// Create and populate a Context for this request
ActionContext context = contextInstance(request, response);
// Create and execute the command.
try {
command.execute(context);
} catch (Exception e) {
// Execute the exception processing chain??
throw new ServletException(e);
}
// Release the context.
context.release();
}
Where did all the steps go? An XML configuration file, chain-config.xml, defines the
sequence of commands to execute. (The exception here is the processMultipart method.
This method, implemented in the ComposableRequestProcessor, wraps the request if
its content type is multipart/form-data). Let's dig into the chain-config.xml file to see
how it works. First, Struts takes advantage of Commons Chain support for sub-chains,
which can represent entire chain as a command, using the LookupCommand. The define
element is used as a convenience mechanism for defining sub-chains using the
LookupCommand.
<define name= "lookup"
className="org.apache.commons.chain.generic.LookupCommand"/>
The ComposableRequestProcessor executes the chain named servlet-standard. This
chain is composed of three commands:
servlet-exception |
A Chain filter for handling exceptions. A filter is a special command that implements a postprocess method. The postprocess method of the filter is called after every command in the chain (actually, its after every command that follows the filter's declaration). |
process-action |
The main sequence for processing the request and executing the appropriate action. |
process-view |
Handles forwarding to the view (e.g., a JSP page). |
<chain name="servlet-standard">
<!-- Establish exception handling filter -->
<command
className="org.apache.struts.chain.commands.ExceptionCatcher"
catalogName="struts"
exceptionCommand="servlet-exception"/>
<lookup
catalogName="struts"
name="process-action"
optional="false"/>
<lookup
catalogName="struts"
name="process-view"
optional="false"/>
</chain>
The process-action chain defines the commands for processing the request and calling
your actions.
<chain name="process-action">
<lookup catalogName="struts"
name="servlet-standard-preprocess"
optional="true"/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectLocale"
/>
<command className=
"org.apache.struts.chain.commands.servlet.RequestNoCache"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SetContentType"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectAction"
/>
<command className=
"org.apache.struts.chain.commands.servlet.AuthorizeAction"
/>
<command className=
"org.apache.struts.chain.commands.servlet.CreateActionForm"
/>
<command className=
"org.apache.struts.chain.commands.servlet.PopulateActionForm"
/>
<command className=
"org.apache.struts.chain.commands.servlet.ValidateActionForm"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectInput"
/>
<command className=
"org.apache.struts.chain.commands.ExecuteCommand"
/>
<command className=
"org.apache.struts.chain.commands.servlet.SelectForward"
/>
<command className=
"org.apache.struts.chain.commands.SelectInclude"
/>
<command className=
"org.apache.struts.chain.commands.servlet.PerformInclude"
/>
<command className=
"org.apache.struts.chain.commands.servlet.CreateAction"
/>
<command className=
"org.apache.struts.chain.commands.servlet.ExecuteAction"
/>
</chain>
The processFoo methods of the original RequestProcessor have been re-implemented
as classes that implement the Chain Command interface. In Struts, each Command
implementation extends from an abstract base class. This base class implements the
execute method of the Command interface. The execute method then calls methods of
the concrete class that perform the actual work.
Consider how the locale is retrieved from the HTTP request and stored. First, here's
implementation of processLocale from the original RequestProcessor:
protected void processLocale(HttpServletRequest request,
HttpServletResponse response) {
// Are we configured to select the Locale automatically?
if (!moduleConfig.getControllerConfig().getLocale()) {
return;
}
// Has a Locale already been selected?
HttpSession session = request.getSession();
if (session.getAttribute(Globals.LOCALE_KEY) != null) {
return;
}
// Use the Locale returned by the servlet container (if any)
Locale locale = request.getLocale();
if (locale != null) {
session.setAttribute(Globals.LOCALE_KEY, locale);
}
}
The new chain implementation processes the locale using two classes,
AbstractSelectLocale and SelectLocale. The abstract class implements the execute
method:
public boolean execute(Context context) throws Exception {
ActionContext actionCtx = (ActionContext) context;
// Are we configured to select Locale automatically?
ModuleConfig moduleConfig = actionCtx.getModuleConfig();
if (!moduleConfig.getControllerConfig().getLocale()) {
return (false);
}
// Retrieve and cache appropriate Locale for this request
Locale locale = getLocale(actionCtx);
actionCtx.setLocale(locale);
return (false);
}
SelectLocale extends AbstractSelectLocale and implements the abstract getLocale
method:
protected Locale getLocale(ActionContext context) {
ServletActionContext saContext = (ServletActionContext) context;
// Has a Locale already been selected?
HttpSession session = saContext.getRequest().getSession();
Locale locale = (Locale) session.getAttribute(Globals.LOCALE_KEY);
if (locale != null) {
return (locale);
}
// Select and cache the Locale to be used
locale = saContext.getRequest().getLocale();
if (locale == null) {
locale = Locale.getDefault();
}
session.setAttribute(Globals.LOCALE_KEY, locale);
return (locale);
}
|
Both the abstract and concrete classes make extensive use of the received context. In
Commons Chain, a Context object acts as the shared store for commands in the chain.
Unlike the RequestProcessor methods, which had direct access to the HTTP servlet
request and response, the Commands have to do a bit more work to find these objects.
The abstract commands downcast the context to an ActionContext. The ActionContext
provides explicit access to Struts-related properties, such as message resources, the Action
being executed, and request and session resources and services. However, the
ActionContext does not depend on the Servlet API. The concrete command classes,
however, will downcast the context to the ServletActionContext. This class
implements the ActionContext interface and wraps a Commons Chain
ServletWebContext. The ServletWebContext contains properties for the servlet
objects such as the HttpServletRequest, HttpServletResponse, and
ServletContext.
The following class diagram shows how the context classes and interfaces used by Struts relate to those provided by Chain.

Figure 1.
The developers of Struts 1.3 have endeavored to reduce coupling with the Servlet API. By structuring its use of the Chain Context as shown here, Struts separates its reliance on the Servlet API to the lowest level, the concrete command implementations. In fact, you will find that the Struts commands follow a very consistent pattern. These commands are implemented such that:
An abstract command implements the Command.execute method and only deals
with the ActionContext.
Within execute, this abstract base class calls a custom abstract method to
perform servlet-specific work.
The concrete subclass implements the abstract method, downcasts the
ActionContext to a ServletActionContext, and uses objects such as the
HttpServletRequest and HttpSession to perform the particular work.
Based on the returned result of the abstract method, the abstract command returns false (to continue the chain), or true (to preempt the chain).
|
Related Reading
|
Understanding the new ComposableRequestProcessor and chain configuration is
important if you want to customize its behavior. Commons Chain affords the developer
multiple ways of customizing Struts' request processing. If you wanted to customize the
locale processing, for example, you could use any of the following techniques:
Provide your own custom extension of AbstractSelectLocale that
implements the getLocale method. Specify this new class as the className in
the chain-config.xml file.
Completely replace the command class with your own class the implements the
Command interface. Specify this new class as the className in the chain-config.xml file.
Finally, utilizing the LookupCommand, you could replace the single command for
locale processing with an entire sub-chain.
If you have ever customized the Struts RequestProcessor, you may have overridden the
processPreprocess method to perform custom request processing. Struts 1.3 uses Chain
to provide a similar hook. The first command in the process-action chain is defined as:
<!-- Look up optional preprocess command -->
<lookup catalogName="struts"
name="servlet-standard-preprocess"
optional="true"/>
This element declares a sub-chain that will be executed as the first step in the process-
action chain. The optional=true attribute ensures that the parent chain will not fail if the
sub-chain is not defined.
In the Jakarta Struts Cookbook, I showed how you can check if a user is logged in by
overriding the processPreprocess method in a custom RequestProcessor. You can
implement this same behavior in Struts 1.3 by defining a pre-processing chain instead of
extending the RequestProcessor. For illustrative purposes, let's see how you would
implement this behavior for the Struts Mail Reader sample application. Here's the command that
checks if the User object is bound to the session. The checkUser parameter indicates if
the check should be made or not.
package com.jadecove.chain.commands;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.struts.apps.mailreader.Constants;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;
public class CheckUser implements Command {
public boolean execute(Context ctx) throws Exception {
ActionContext context = (ActionContext) ctx;
Object user = context.getSessionScope().get(Constants.USER_KEY);
if (user == null &&
context.getParameterMap().containsKey("checkUser")) {
HttpServletResponse response =
((ServletActionContext) ctx).getResponse();
response.sendError(403, "User not logged in.");
return true;
}
return false;
}
}
Now we need to declare the chain containing this command. But where should you add the XML for the new chain? There are a couple of options here; first, you could add the command to the chain-config.xml file provided by Struts. This is the most straightforward approach. However, when you upgrade Struts, you will have to ensure that you preserve the changes you made to this file. A better alternative is to create a separate file for your custom sub-chains, then tell Struts to use your chain configuration file along with the Struts file. First, create a file in your application's WEB-INF folder, named custom- chain-config.xml. Add the following chain declaration to the file:
<?xml version="1.0" ?>
<catalog name="struts">
<chain name="servlet-standard-preprocess">
<command className="com.jadecove.chain.commands.CheckUser"/>
</chain>
</catalog>
Then you configure the ActionServlet to use your custom chain configuration file along
with the Struts-provided file, much like you do for specifying struts-config.xml files.
<!-- Action Servlet Configuration -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>
org.apache.struts.action.ActionServlet
</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>
/WEB-INF/struts-config.xml,
/WEB-INF/struts-config-registration.xml
</param-value>
</init-param>
<init-param>
<param-name>chainConfig</param-name>
<param-value>
/WEB-INF/chain-config.xml,
/WEB-INF/custom-chain-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
|
When you access the Mail Reader's welcome page, it will look like Figure 2:

Figure 2.
But when you add the checkUser parameter, the CheckUser command generates the
response and terminates the chain, as shown in Figure 3.

Figure 3.
If you'd rather replace a command then create a separate chain, simply make the change in the chain-config.xml file.
Before I put a wrap on Struts and Chain, let's take a look at how Struts uses a Chain filter for exception handling. Struts defines an exception catching command as the first element in the servlet-standard chain:
<!-- Establish exception handling filter -->
<command className="org.apache.struts.chain.commands.ExceptionCatcher"
catalogName="struts"
exceptionCommand="servlet-exception"/>
We have already seen how the command element uses the className attribute; however
the catalogName and exceptionCommand attributes are new. In fact, there is nothing
magical about these two attributes. They have no intrinsic meaning to Chain. When
Commons Chain parses the chain configuration (using Commons Digester under the
covers), any attributes not explicitly handled by Chain are treated as JavaBean property
setters. In other words, when Chain parses the command above, it will acquire an
instance of the ExceptionCatcher class, and then set JavaBean properties as if the
following code was used:
exceptionCatcher.setCatalogName("struts");
exceptionCatcher.setExceptionCommand("servlet-exception");
The ExceptionCatcher implements the filter. Classes that implement the filter must
implement the following two methods:
// (From the Command interface) Called when the Filter is first
// processed in sequence
public boolean execute(Context context);
// Called after a chain command throws an exception
// or the chain reaches its end
public boolean postprocess(Context context, Exception exception);
In the ExceptionCatcher, the execute method simply resets the command, setting the
current exception in the ActionContext to null and returning false (instructing the chain
to continue). The postprocess method is where all the interesting stuff happens. If this
method receives a non-null exception, it uses the catalogName and exceptionCommand
properties to find and execute a command (which can itself be a chain of commands).
public boolean postprocess(Context context, Exception exception) {
// Do nothing if there was no exception thrown
if (exception == null) {
return (false);
}
// Store the exception in the context
ActionContext actionCtx = (ActionContext) context;
actionCtx.setException(exception);
// Execute the specified command
try {
Command command = lookupExceptionCommand();
if (command == null) {
throw new IllegalStateException
("Cannot find exceptionCommand '" +
exceptionCommand + "'");
}
command.execute(context);
} catch (Exception e) {
throw new IllegalStateException
("Exception chain threw exception");
}
return (true);
}
Where's the good old Struts exception handler? Well, it's encapsulated in the configuration of the servlet-exception chain.
<!-- ========== Servlet Exception Handler Chain ============= -->
<chain name="servlet-exception">
<!-- Execute the configured exception handler (if any) -->
<command className=
"org.apache.struts.chain.commands.servlet.ExceptionHandler"
/>
<!-- Follow the returned ForwardConfig (if any) -->
<command className=
"org.apache.struts.chain.commands.servlet.PerformForward"
/>
</chain>
The classes used in this chain perform the same function as the Struts 1.2 exception
handler. Using the ExceptionHandler class, which extends
AbstractExceptionHandler, the first command locates the declared Struts exception
handler (if any) and calls that handler's execute method. The returned ActionForward is
stored in the ActionContext and subsequently processed by the general-purpose
PerformForward command.
I hope that I have been able to give you a better idea of how Struts uses chains. Both Commons Chain and Struts 1.3 are still in flux, so things may change. I encourage you to download the latest source for Struts and take a good look at how it uses Chain. I think you will find that the implementation is well-thought-out; adding custom behavior to Struts request processing is now much easier and less obtrusive than ever before.
Bill Siggelkow is an independent consultant specializing in software design, development, and technical training.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.