A Look at Commons Chain, Part 2
by Bill Siggelkow, author of Jakarta Struts Cookbook03/09/2005
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 + "'");
}
}