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

by Bill Siggelkow, author of Jakarta Struts Cookbook
03/02/2005

As developers, we are often required to apply object-oriented approaches to systems that are procedural in nature. Business analysts and managers illustrate such systems using flowcharts and workflow diagrams instead of class hierarchies and sequence diagrams. Object orientation, however, brings flexibility into the mix when applied to solving these problems. Object-oriented design patterns, such as the Template Method [GoF] and Chain of Responsibility [GoF], provide useful structures and behaviors for representing sequential processing.

The Jakarta Commons subproject Chain codifies and combines these patterns into a reusable Java framework for representing sequential process flows. This framework, developed under the community umbrella of the Jakarta Commons project, has been quietly gaining acceptance and usage in a number of interesting applications, most notably as the underlying mechanism for handling HTTP request processing in the Struts and Shale web application frameworks. You can use Commons Chain in situations where you need to define and execute a sequential set of steps.

With respect to the classic design patterns, developers and architects commonly apply the Template Method pattern for modeling sequential processing. With the Template Method, an abstract parent class defines the algorithm used: the steps of the process. It's up to the subclasses to provide the implementation. Alternatively, the parent class can be a concrete class that provides a default implementation of the algorithm's methods.

Because the Template Method relies on inheritance--subclasses must inherit from the algorithm-defining parent class--software that uses this pattern tends to exhibit tight coupling and less flexibility. Because concrete classes must extend the parent class to add custom behavior, you limit the flexibility of the design--you are locked into the class hierarchy. Commons Chain solves this problem by allowing for the algorithm to be defined through a configuration file interpreted at runtime.

To see how Commons Chain works, let's start with a somewhat contrived example: the business process employed by purveyors of pre-owned vehicles (a.k.a., used car salespeople). Here are the steps that compose the sales process:

  1. Get customer information
  2. Test-drive vehicle
  3. Negotiate sale
  4. Arrange financing
  5. Close sale

Now suppose that you wanted to model this flow using the Template Method pattern. You could create an abstract class--defining the algorithm--that looks something like this:


public abstract class SellVehicleTemplate {
	public void sellVehicle() {
        getCustomerInfo();
        testDriveVehicle();
        negotiateSale();
        arrangeFinancing();
        closeSale();
	}

	public abstract void getCustomerInfo();
	public abstract void testDriveVehicle();
	public abstract void negotiateSale();
	public abstract void arrangeFinancing();
	public abstract void closeSale();	
}

Now let's see how you could implement this process using Commons Chain. First, download Commons Chain. You can grab the latest nightly download as a .zip or .tar file, or you can acquire the most up-to- date code by checking out the Commons Chain module from the CVS or SubVersion source repositories. Extract the archive, placing the commons-chain.jar file on your classpath.

To implement the business process using Commons Chain, implement each step in the process as a class that has a single public "do it all" method named execute(). This is a traditional usage of the Command pattern. Here's a simple implementation of the "Get customer information" step.


package com.jadecove.chain.sample;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

public class GetCustomerInfo implements Command {
	public boolean execute(Context ctx) throws Exception {
		System.out.println("Get customer info");
		ctx.put("customerName","George Burdell");
		return false;
	}
}

For illustration purposes, this class doesn't do much. However, it does store the customer's name in the Context. The Context object provides the glue between commands. For the time being, think of the Context as nothing more than a hash table that you can stuff values into, and pull values out of, by key. All subsequent commands can now access this data. The TestDriveVehicle, NegotiateSale, and ArrangeFinancing command classes are simple implementations that simply print out what the command would do.


package com.jadecove.chain.sample;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

public class TestDriveVehicle implements Command {
	public boolean execute(Context ctx) throws Exception {
		System.out.println("Test drive the vehicle");
		return false;
	}
}

public class NegotiateSale implements Command {
	public boolean execute(Context ctx) throws Exception {
		System.out.println("Negotiate sale");
		return false;
	}
}

public class ArrangeFinancing implements Command {
	public boolean execute(Context ctx) throws Exception {
		System.out.println("Arrange financing");
		return false;
	}
}

The CloseSale implementation uses the context to extract the customer's name, set in the GetCustomerInfo command.


package com.jadecove.chain.sample;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

public class CloseSale implements Command {
	public boolean execute(Context ctx) throws Exception {
		System.out.println("Congratulations "
                  +ctx.get("customerName")
			+", you bought a new car!");
		return false;
	}
}

Now you can define the process as a sequence or "chain of commands."


package com.jadecove.chain.sample;

import org.apache.commons.chain.impl.ChainBase;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ContextBase;

public class SellVehicleChain extends ChainBase {
	public SellVehicleChain() {
		super();
		addCommand(new GetCustomerInfo());
		addCommand(new TestDriveVehicle());
		addCommand(new NegotiateSale());
		addCommand(new ArrangeFinancing());
		addCommand(new CloseSale());
	}
	public static void main(String[] args) throws Exception {
		Command process = new SellVehicleChain();
		Context ctx = new ContextBase();
		process.execute(ctx);
	}
}

Running the main method results in the following output:


Get customer info
Test drive the vehicle
Negotiate sale
Arrange financing
Congratulations George Burdell, you bought a new car!

Pages: 1, 2, 3

Next Pagearrow