“A popular technique for organizing the execution of complex processing flows is the “Chain of Responsibility” pattern, as described (among many other places) in the classic “Gang of Four” design patterns book. Although the fundamental API contracts required to implement this design patten are extremely simple, it is useful to have a base API that facilitates using the pattern, and (more importantly) encouraging composition of command implementations from multiple diverse sources.”
That is what Apache has to say as an intro to its Commons Chain API
It is no brainer, really, but Apache APIs do make it simpler to configure, implement and execute multiple Chains of Responsibilities. One thing, however, that feels “off”, when (if) implementing “Commons Chain” side by side with Spring, is there is no flexible, consistent (with Spring) way to setup Command objects: inject Command properties / refer to a single Command class as a bean, instead of duplicating the class name in Apache Chains, and Spring configuration, if Command object needs to be reused, etc…
Here is a simple way to utilize all the power that Apache offers + make it Spring friendly. Five components will be used in this example – 1 core class, 2 command classes, 1 test (driver) and Spring configuration file. Let’s see if you can figure out which is which :):
As you can see we are going to play an ultra short session of table tennis, or how it is sometimes called “Ping Pong”. It is going to be quite short, because we have two command objects: Ping and Pong, which are going to be chained, and run (by ChainRunner) once each: “Ping -> Pong”.
First, instead of relying on Apache way to configure chains, to make it consistent with all other application beans (classes) we’ll keep chain(s) configuration in Spring:
chain-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<bean name="pingPongChain" class="org.apache.commons.chain.impl.ChainBase">
<constructor-arg>
<util:list>
<ref bean="pingCommand" />
<ref bean="pongCommand" />
</util:list>
</constructor-arg>
</bean>
<bean name="pingCommand" class="org.dotkam.samples.chain.command.PingCommand">
<constructor-arg value="ping"/>
</bean>
<bean name="pongCommand" class="org.dotkam.samples.chain.command.PongCommand">
<constructor-arg value="pong"/>
</bean>
<bean id="chainRunner" class="org.dotkam.samples.chain.ChainRunner"/>
</beans> |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<bean name="pingPongChain" class="org.apache.commons.chain.impl.ChainBase">
<constructor-arg>
<util:list>
<ref bean="pingCommand" />
<ref bean="pongCommand" />
</util:list>
</constructor-arg>
</bean>
<bean name="pingCommand" class="org.dotkam.samples.chain.command.PingCommand">
<constructor-arg value="ping"/>
</bean>
<bean name="pongCommand" class="org.dotkam.samples.chain.command.PongCommand">
<constructor-arg value="pong"/>
</bean>
<bean id="chainRunner" class="org.dotkam.samples.chain.ChainRunner"/>
</beans>
A “pingPongChain” is configured as “org.apache.commons.chain.impl.ChainBase”, and have two Commad classes, that implement “org.apache.commons.chain.Command” interface, wired in.
NOTE: The best practice is to keep Commands either stateless, or, if they have to have a state – immutable. If this practice is followed, these Commands can be safely reused throughout many chains (or even as stand alone utilities). That is the reason, in configuration above, parameters are injected at Command’s creation time via constructor injection.
In this example Commands are ultra simple for clarity:
PingCommand.java:
package org.dotkam.samples.chain.command;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
public class PingCommand implements Command {
private String message;
public PingCommand( String message ) {
this.message = message;
}
public boolean execute( Context context ) throws Exception {
System.err.println( message );
return false;
}
} |
package org.dotkam.samples.chain.command;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
public class PingCommand implements Command {
private String message;
public PingCommand( String message ) {
this.message = message;
}
public boolean execute( Context context ) throws Exception {
System.err.println( message );
return false;
}
}
and PongCommand.java:
package org.dotkam.samples.chain.command;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
public class PongCommand implements Command {
private String message;
public PongCommand( String message ) {
this.message = message;
}
public boolean execute( Context context ) throws Exception {
System.err.println( message );
return false;
}
} |
package org.dotkam.samples.chain.command;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
public class PongCommand implements Command {
private String message;
public PongCommand( String message ) {
this.message = message;
}
public boolean execute( Context context ) throws Exception {
System.err.println( message );
return false;
}
}
Now let’s look at the heart of the “Spring Chainer” – the “ChainRunner.java”:
package org.dotkam.samples.chain;
import org.apache.commons.chain.impl.ChainBase;
import org.apache.commons.chain.impl.ContextBase;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class ChainRunner implements BeanFactoryAware {
private BeanFactory beanFactory;
public void runChain( String chainName ) {
try {
createChain ( chainName ).execute( new ContextBase() );
}
catch ( Exception exc ) {
throw new RuntimeException(
"Chain \"" + chainName + "\": Execution failed.", exc );
}
}
public void setBeanFactory( BeanFactory beanFactory ) throws BeansException {
this.beanFactory = beanFactory;
}
protected ChainBase createChain( String chainName ) {
return ( ChainBase ) this.beanFactory.getBean( chainName );
}
} |
package org.dotkam.samples.chain;
import org.apache.commons.chain.impl.ChainBase;
import org.apache.commons.chain.impl.ContextBase;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class ChainRunner implements BeanFactoryAware {
private BeanFactory beanFactory;
public void runChain( String chainName ) {
try {
createChain ( chainName ).execute( new ContextBase() );
}
catch ( Exception exc ) {
throw new RuntimeException(
"Chain \"" + chainName + "\": Execution failed.", exc );
}
}
public void setBeanFactory( BeanFactory beanFactory ) throws BeansException {
this.beanFactory = beanFactory;
}
protected ChainBase createChain( String chainName ) {
return ( ChainBase ) this.beanFactory.getBean( chainName );
}
}
It only does a couple of things, really.
By implementing Spring’s “BeanFactoryAware” interface, at runtime ChainRunner will have a reference to a “beanFactory” which it is going to use to obtain a reference to the requested chain via “createChain” method.
“ChainRunner” has an entry point which is a “runChain” method, that executes a chain by chain name (bean name in configuration file). For example, in the configuration shown above “pingPongChain” name can be provided.
It would ideally need to use a couple of custom runtime exceptions: e.g. ChainNotFoundException, IsNotChainException and ChainExecutionException, but we’ll keep it short here for clarity.
Alternatively, to strong type chains a bit, “ChainRunner” could take a Map of chains with keys as chain names, and corresponding “ChainBase” chain objects as values.
“ChainRunner” is pretty much everything that is needed in order to configure an Apache chain in Spring. We can run it by creating a JUnit (not really a test, just a driver):
ChainTest.java:
package org.dotkam.samples.chain;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations={ "/conf/chain-config.xml"} )
public class ChainTest {
@Resource
ChainRunner chainRunner;
@Test
public void driveTheChain() {
System.out.println("Starting up... [Ok]");
chainRunner.runChain( "pingPongChain" );
System.out.println("Finised... [Ok]");
}
} |
package org.dotkam.samples.chain;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations={ "/conf/chain-config.xml"} )
public class ChainTest {
@Resource
ChainRunner chainRunner;
@Test
public void driveTheChain() {
System.out.println("Starting up... [Ok]");
chainRunner.runChain( "pingPongChain" );
System.out.println("Finised... [Ok]");
}
}
And here is the test/driver result:
Good start! Now it is time to sign up and see your name at the top of The International Table Tennis Federation (ITTF) World Ranking. Good Luck! :)