ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

A Look at Commons Chain, Part 2

by Bill Siggelkow, author of Jakarta Struts Cookbook
03/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 + "'");
    }
}

Pages: 1, 2, 3, 4

Next Pagearrow