A Look at Commons Chain, Part 2
Pages: 1, 2, 3, 4
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.executemethod and only deals with theActionContext.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
ActionContextto aServletActionContext, and uses objects such as theHttpServletRequestandHttpSessionto 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
Jakarta Struts Cookbook |
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
AbstractSelectLocalethat implements thegetLocalemethod. Specify this new class as theclassNamein the chain-config.xml file.Completely replace the command class with your own class the implements the
Commandinterface. Specify this new class as theclassNamein 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>