A Look at Commons Chain, Part 1
Pages: 1, 2, 3
Before going further, let's take a look at the classes and interfaces of Commons Chain that we used.

Figure 1.
The relationship between the Command and Chain classes exemplifies the Composite
pattern [GoF]; a chain is not only composed of commands, but is itself a command. This
allows you to easily replace single commands with entire sub-chains. The method defined
by the Command object's single operation represents a straightforward command:
public boolean execute(Context context);
The context is nothing more than a collection of name-value pairs. The Context interface
serves as a marker interface: it extends java.util.Map but does not add any additional
behavior. The ContextBase class, on the other hand, not only provides the Map
implementation, but it also adds a characteristic known as attribute-property transparency.
This characteristic allows you to access JavaBean properties, defined with traditional
getFoo and setFoo methods, using the put and get methods defined by the Map interface.
Values stored using a JavaBean "setter" method can be retrieved, by property name,
using the Map's get method. Conversely, values stored using the Map's put method can
be retrieved using the JavaBean "getter" method.
For our example, we can create a specialized context providing
explicit support for the customerName property.
package com.jadecove.chain.sample;
import org.apache.commons.chain.impl.ContextBase;
public class SellVehicleContext extends ContextBase {
private String customerName;
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String name) {
this.customerName = name;
}
}
Now you can use, with equal interoperability, the generic attributes of the Map along
with the explicit JavaBean accessor and mutator methods. But first, you need instantiate
the SellVehiceContext instead of ContextBase when you run the SellVehicleChain:
public static void main(String[] args) throws Exception {
Command process = new SellVehicleChain();
Context ctx = new SellVehicleContext();
process.execute(ctx);
}
Though you didn't change how GetCustomerInfo stores the customer name--it still uses
ctx.put("customerName", "George Burdell")--you can get the customer's name in the
CloseSale class using the getCustomerName() method.
public boolean execute(Context ctx) throws Exception {
SellVehicleContext myCtx = (SellVehicleContext) ctx;
System.out.println("Congratulations "
+ myCtx.getCustomerName()
+ ", you bought a new car!");
return false;
}
Those commands that rely on type safety and explicit properties of the context can utilize
the traditional property getter and setter methods. As new commands are added, they can
be written without regard to the specific context implementation, provided that the
properties are accessed through the standard Map get and put methods. Regardless of
which mechanism is used, the ContextBase class ensures that the commands can
interoperate through the shared context.
This example shows how you can use the Commons Chain API to create and execute a sequence of commands. Of course, like almost every new piece of software written in Java these days, Commons Chain can be configured via an XML file. Applying this capability to the "sell vehicle" process, you can now define the sequence of commands in an XML file. The canonical name for this file is chain-config.xml.
<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>
The Chain configuration file can contain multiple chain definitions grouped together into catalogs. For this example, the chain definition is defined within the default catalog. You can, in fact, have multiple named catalogs within this file, each with its own set of chains.
Now, instead of defining the sequence of commands as was done in the
SellVehicleChain, you load the catalog and retrieve the named chain using classes
provided by Commons Chain.
package com.jadecove.chain.sample;
import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.config.ConfigParser;
import org.apache.commons.chain.impl.CatalogFactoryBase;
public class CatalogLoader {
private static final String CONFIG_FILE =
"/com/jadecove/chain/sample/chain-config.xml";
private ConfigParser parser;
private Catalog catalog;
public CatalogLoader() {
parser = new ConfigParser();
}
public Catalog getCatalog() throws Exception {
if (catalog == null) {
parser.parse(this.getClass().getResource(CONFIG_FILE));
}
catalog = CatalogFactoryBase.getInstance().getCatalog();
return catalog;
}
public static void main(String[] args) throws Exception {
CatalogLoader loader = new CatalogLoader();
Catalog sampleCatalog = loader.getCatalog();
Command command = sampleCatalog.getCommand("sell-vehicle");
Context ctx = new SellVehicleContext();
command.execute(ctx);
}
}
Chain uses the Commons Digester to read and parse the configuration file. To use this
capability, you will need to add the Commons Digester .jar file to your classpath. I used
version 1.6 and had no problems. Digester depends on Commons Collections (I used
version 3.1), Commons Logging (version 1.0.4), and Commons BeanUtils 1.7.0. You will
need to add these .jars to your classpath, as well. After adding these .jar files to my
classpath, the CatalogLoader successfully compiled and ran. The output is exactly like
that generated by the other two tests.