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

advertisement

AddThis Social Bookmark Button

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
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.

Pages: 1, 2, 3

Next Pagearrow