Hook on to Jakarta Commons Chain

New Commons framework implements the Chain of Responsibility pattern

The Jakarta Commons project encompasses several subprojects, which contain reusable Java components. Of these, the Logging, Validator, and Digester subprojects are the best known, if only because Struts uses them. A fairly new Commons subproject, still in development, is Chain. The Chain framework is an implementation of the Chain of Responsibility pattern, used to execute a flow of sequential process steps. In the near future, Chain will also be used by Struts (not entirely coincidental—some Struts committers are also Chain committers).

In this article, I explain Chain's usage, advantages, and current imperfections.

Chains, commands, and catalogs

In the Chain framework, units of work within a sequential flow of steps are represented by commands. A sequence of commands forms a chain. The chain itself is also a command that can be executed. Chains are kept in catalogs, from which they can be retrieved at the time of execution. For this purpose, the Chain framework offers the interfaces Chain, Command, and Catalog, see the figure below for a class diagram.

Class diagram of some Chain interfaces

The Chain framework itself contains ready-to-use implementations of Chain and Catalog. You still must implement the commands containing the functionality that forms a unit of work. See Listing 1 for a simple example.

Listing 1. An implementation of a command

 public class ACommand1 implements Command {
   public boolean execute(Context ctx) throws Exception {
      //Add functionality for ACommand1
      String property = (String) ctx.get("property");
      System.out.println("A command1: " + property);
      return false; //The chain will resume
   }
}

An application using the Chain framework must instantiate a Catalog with chains and commands. This can be done directly by using the Chain API or by using a configuration file. The latter option offers the most flexibility. See Listing 2 for a simple configuration file.

Listing 2. A chain-config.xml file

 <catalog>
   <chain name="a-chain">
      <command className="chain.test.ACommand1"/>
      <command className="chain.test.ACommand2"/>
   </chain>
</catalog> 

From this point, applying the Chain framework to execute the configured commands isn't hard, as Listing 3 demonstrates.

Listing 3. The execution of a chain

 

public class ChainStart {

public static void main(String[] args) { ChainStart cs = new ChainStart(); //Get the catalog Catalog catalog = cs.parseConfigFile(); //Get the command Command command = catalog.getCommand("a-chain"); try { //Create a context YourContext ctx = new YourContext(); ctx.setProperty("a-value"); //Execute the command command.execute(ctx); } catch (Exception exc) { System.out.println(exc.toString()); } } private Catalog parseConfigFile() { //Parse the configuration file ConfigParser parser = new ConfigParser(); String fileLocation = "/chain/test/chain-config.xml"; try { parser.parse(this.getClass().getResource(fileLocation)); } catch (Exception exc) { System.out.println(exc.toString()); } return CatalogFactoryBase.getInstance().getCatalog(); } }

The ConfigParser can be used to instantiate the Catalog. The Catalog is stored in the singleton CatalogFactoryBase. Because the Catalog doesn't have a name attribute in the configuration file, it automatically is CatalogFactory's default catalog. Therefore, it can be retrieved by the getCatalog() method. Only chains or commands that are direct children of Catalog can be retrieved by the getCommand() method. So, the command ACommand1 cannot be retrieved directly from Catalog. If desired, you can configure ACommand1 as a direct child of Catalog. In that case, Catalog should also have a name attribute.

The executed command in the example above is a chain, which, in turn, executes the underlying commands as configured. As soon as a command returns true or whenever an exception occurs, the chain's execution stops. If exception handling within the chain is needed, the Chain framework offers the interface Filter. From this interface, the postprocess() method is invoked whenever an exception is thrown in the chain.

The context

Chains and commands are always executed within a certain context. In Listing 3, a context is passed to the execute() method, which is propagated to all commands that are part of the chain. The Context interface is an extension of the Map interface, but doesn't add any extra methods. The Chain framework offers ContextBase as a useful implementation of the Context interface. Listing 4 elaborates on Listing 3, with an extension of ContextBase.

Listing 4. An extension of ContextBase

 

public class YourContext extends ContextBase {

private String property;

public String getProperty() { return property; } public void setProperty(String string) { property = string; } }

The chain's caller passes YourContext, the property of which is set using the setter. The chain's commands do not need to know Context's implementation. They can just retrieve the property with get("property"). In spite of this flexibility, type-safety is ensured. The command can assign a new value to the property with ctx.put("property", "a-new-value"), but ctx.put("property", new Object()) yields an UnsupportedOperationException. The ContextBase uses introspection for the implementation of the get() and put() methods.

Chain in Web applications

The Chain framework offers several packages for its support in Web applications, as shown in Listing 5. The following chain-config.xml file is placed in the WEB-INF directory.

Listing 5. A chain-config.xml file

 <catalog name="a-catalog">
   <chain name="a-chain">
      <command className="chain.test.ACommand1"/>
      <command className="chain.test.ACommand2"/>
      </chain>
   <command name="the-apache-request-parameter-mapper"
         className="org.apache.commons.chain.web.servlet.RequestParameterMapper"/>
</catalog> 

In the web.xml file, the following is configured:

Listing 6. A web.xml file

 

<context-param> <param-name>org.apache.commons.chain.CONFIG_WEB_RESOURCE</param-name> <param-value>/WEB-INF/chain-config.xml</param-value> </context-param>

<servlet> <servlet-name>chain</servlet-name> <servlet-class>org.apache.commons.chain.web.servlet.ChainProcessor </servlet-class> <init-param> <param-name>org.apache.commons.chain.CATALOG</param-name> <param-value>a-catalog</param-value> </init-param> <init-param> <param-name>org.apache.commons.chain.COMMAND</param-name> <param-value>the-apache-request-parameter-mapper </param-value> </init-param> </servlet>

<servlet-mapping> <servlet-name>chain</servlet-name> <url-pattern>*.chain</url-pattern> </servlet-mapping>

In web.xml, a ChainProcessor servlet is configured. After the Chain framework initializes this servlet, the chain-config.xml will be read as is, configured with the context init-param. The configuration will be stored in the CatalogFactory. The init-params in this servlet are used to determine which command from which catalog from the chain-config.xml must be executed when handling an HTTP request. The service() method handles HTTP requests so the doGet() and doPost() methods are no longer of any importance.

In Listing 6, the special command RequestParameterMapper, supplied by the Chain framework, is executed. Using this command, you can control, through a request parameter, which command the RequestParameterMapper must execute. By using the context-relative path /test.chain?command=a-chain, you can execute the Chain a-chain from the chain-config.xml. Since you can configure several catalogs in a chain-config.xml, paths can be flexibly linked to commands or chains, which is comparable to mapping a path to an action in Struts.

The ChainProcessor passes a ServletWebContext by default as a context to the command being executed. Via the ServletWebContext, you have access to the standard servlet context, request, and response objects. In addition, all functionality described for the ContextBase is offered.

Chain and Struts

The examples in this article give a pretty good impression of the Chain framework's possibilities, but show a command only retrieving and printing a property. An example of how Chain can be practically applied can be found in Struts, to be more precise, in the upcoming version 1.3. In this version, you can use a RequestProcessor, implemented by the Chain framework, but still offering the same functionality. A RequestProcessor processes requests according to your configuration. For example, it populates ActionForm with request parameters, it determines which Action to execute and to which servlet or JSP (JavaServer Pages) page the request should be forwarded. Below is a simplified example of how the process() method is currently implemented in the RequestProcessor.

Listing 7. A simplified representation of the process() method

 public void process(HttpServletRequest request,
                    HttpServletResponse response)
                            throws IOException, ServletException {
   processMultipart(request);
   processPath(request, response);
   processLocale(request, response);
   processContent(request, response);
   processNoCache(request, response);
   processPreprocess(request, response));
   processMapping(request, response, path);
   processRoles(request, response, mapping)); 
   processActionForm(request, response, mapping);
   processPopulate(request, response, form, mapping);
   processValidate(request, response, form, mapping));
   processForward(request, response, mapping));
   processInclude(request, response, mapping;
   processActionCreate(request, response, mapping);
   processActionPerform(request, response, action, form, mapping);
   processForwardConfig(request, response, forward);
} 

It is clear that the process() method executes many distinct steps that may or may not be successful. In Chain framework terminology, you could say that the process() method executes a chain of commands. In Struts 1.3, process() will be implemented by using a chain of commands. The Struts committers want to achieve easier and more flexible manufacturing of RequestProcessor implementations.

Bugs and imperfections

As mentioned in the introduction, Chain is a fairly new framework. At the time of this writing, several bugs and imperfections remain. Also, the complete Javadoc is still not available. So that the given examples are complete, I mention the following points already known to the Chain committers.

The Javadoc indicates that the CONFIG_WEB_RESOURCE should be configured as servlet init-param. In its current implementation, however, it must be implemented as context init-param. I expect the implementation will change soon.

Currently, there's a bug in the way the RequestParameterMapper executes. I expect a quick patch. For now, the fastest way to circumvent the bug is the following implementation of RequestParameterMapper:

Listing 8. A workaround for bypassing a bug

 

public class ReqParamMapperFixed extends RequestParameterMapper {

public boolean execute(Context context) throws Exception {

// Look up the specified request parameter for this request ServletWebContext swcontext = (ServletWebContext) context; HttpServletRequest request = swcontext.getRequest(); String value = request.getParameter(getParameter());

// Map to the Command specified by the extra path info Catalog catalog = (Catalog) ((ServletWebContext) context).getRequest() .getAttribute(getCatalogKey()); Command command = catalog.getCommand(value); return (command.execute(context)); } }

Conclusion

In this article, I described how you can use the Chain framework to implement the Chain of Responsibility pattern. You can use Commands to implement your units of work. Although the flow can be assembled programmatically, this article described how to do this declaratively in a configuration file. The Chain framework is still under development. Therefore, I suggest you review the Jakarta site for the latest developments.

Eric Gunnewegh is a Java technology specialist at the Dutch office of Sogeti. He has written many articles and has given presentations about the various Jakarta subprojects, including the Jakarta Digester framework. Gunnewegh has been working with Java technology since 1999 and is a Sun Certified Java Programmer, Web Component Developer, and Business Component Developer.
1 2 Page 1
Page 1 of 2