A Look at Commons Chain, Part 2
Pages: 1, 2, 3, 4
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);
}