Using Commons SCXML with Groovy

October 16, 2011

The Watch example is rewritten using Groovy. The actual state machine document is created using Groovy‘s MarkupBuilder.

More then a decade ago I used a state machine approach to develop a web application framework. It worked, which surprised me most of all, lol! Since then I’ve wondered why there is no generic reuseable facility to use this approach.

SCXML is “…a general-purpose event-based state machine language that combines concepts from CCXML and Harel State Tables”. It would seem to fit the bill.

Many applications would probably benefit from having the control flow managed by some kind of flow engine. Sometimes we wind up with the internal object’s implicit FSM mixed into the application FSM in tangles of confusion and brittleness.

Modern software engineering is just the use of more ingenious complex frameworks for obfuscating the finite state machines really being created. This is especially true in the Java world, where there is a tendency toward frameitis.

Listing one below is a version of the Watch example presented on the Commons SCXML site. Instead of subclassing the AbstractStateMachine, this version uses the SCXMLExecutor and extends SCXMLListener. The listener allows the engine to invoke methods on the demo class by setting itself as the invoker: executor.registerInvokerClass(“watch”,getClass());

Note: The Groovy Grab will not work in Eclipse. I just put the required jars in the build path. The grab will work in the command line.

Listing one

// file: ScxmlDemo.groovy
import groovy.xml.*
import groovy.grape.*

import java.awt.datatransfer.Transferable;
import java.lang.reflect.Method
import org.apache.commons.scxml.*
import org.apache.commons.scxml.io.*
import org.apache.commons.scxml.env.*
import org.apache.commons.scxml.invoke.*
import org.apache.commons.scxml.model.*
import org.apache.commons.scxml.env.jsp.*

@Grapes(
	[@Grab('commons-scxml:commons-scxml:0.9'),
	@Grab('commons-el:commons-el:1.0'),
	@Grab('commons-logging:commons-logging:1.1.1')]
)

/**
 * 
 * Demo of 
 * "...State Chart XML (SCXML), which is a general-purpose 
 * event-based state machine language that combines 
 * concepts from CCXML and Harel State Tables."
 * 
 * Invocation:
 *   groovy src\ScxmlDemo.groovy
 * @author jbetancourt
 * 
 */
public class ScxmlDemo implements SCXMLListener {
	SCXMLExecutor executor
	def namespace = "http://www.w3.org/2005/07/scxml"
	Map methods = [:]
	
	enum Trans{
		TSTART("watch.start"),TSPLIT("watch.split"),
		TSTOP("watch.stop"),TUNSPLIT("watch.unsplit"),
		TRESET("watch.reset");
				
		String name;
		public Trans(String s){
			name = s
		}
	}

	/**   */
	public ScxmlDemo(){
		["doReset","doRunning","doPaused","doStopped"].
		each{
			methods.put(it,
				this.getClass().
				getDeclaredMethod(it, new Class[0]))
		}
	}

	/** Instead of writing XML, we use a DSL builder to create it */
	def createScxmlDoc(){
		def writer = new StringWriter()
		def builder = new MarkupBuilder(writer)

		builder.scxml(
		  xmlns:namespace,version:"1.0",initialstate:"reset"){
		     state(id:"reset"){
			   transition(
				event:Trans.TSTART.name,target:"running"){
			   }
		     }
		     state(id:"running"){
			   transition(
				event:Trans.TSPLIT.name,target:"paused"){
		           }
		           transition(
			        event:Trans.TSTOP.name,target:"stopped"){
			   }
		     }
		     state(id:"paused"){
			    transition(
			        event:Trans.TUNSPLIT.name,target:"running"){
			    }
			    transition(
				event:Trans.TSTOP.name,target:"stopped"){
			    }
				}
		     state(id:"stopped"){
			   transition(
				event:Trans.TRESET.name,target:"reset"){
			   }
		     }
		}

		new File("sm.xml").setText(writer.toString())
		writer.close()
	}

	/**   */
	public void execute(){
		createScxmlDoc()
		def eh = new
			org.apache.commons.scxml.env.SimpleErrorHandler()
		SCXML model = SCXMLParser.parse(
				new File("sm.xml").toURI().toURL(), eh)

		executor = new SCXMLExecutor()
		executor.setRootContext(new SimpleContext())
		executor.setEvaluator(new ELEvaluator())
		executor.setErrorReporter(new SimpleErrorReporter())
		executor.setStateMachine(model)
		executor.setEventdispatcher(new SimpleDispatcher())
		executor.addListener(model, this)
		executor.registerInvokerClass("watch",getClass());

		executor.go()

		fireEvent(Trans.TSTART.name);
		def status = executor.getCurrentStatus().
				getAllStates()
		status.each{ println "Now state is: ${it.id}" }
	}

	/**  */
	public void fireEvent(name){
		def evts = [
			new TriggerEvent(name,
			TriggerEvent.SIGNAL_EVENT, null)
		];

		executor.triggerEvents(evts as TriggerEvent[])
	}

	/**  */
	def invoke(id){
		try {
			def name = 'do' + id.substring(0,1).
				toUpperCase() + id.substring(1)
			def method = methods[name]
			method.invoke(this, new Object[0]);
			return true
		}catch(Exception ex){
			ex.printStackTrace()
		}
		
		return false
	}

	/**  */
	@Override
	public void onEntry(
	final TransitionTarget entered) {
		invoke(entered.getId());
	}

	/**
	 *
	 * @param from The "source" transition target.
	 * @param to The "destination" transition target.
	 * @param transition The transition being followed.
	 */
	@Override
	public void onTransition(
	   final TransitionTarget from,
	   final TransitionTarget to,
	   final Transition transition) {
		// nothing to do
	}

	/**
	 *
	 * @param exited The transition target being exited.
	 */
	@Override
	public void onExit(final TransitionTarget exited) {
		// nothing to do
	}

	// the activities
	def show(s){println "I'm '$s' ..."}
	public void doReset() {show('reset')}
	public void doRunning() {show('running')}
	public void doPaused() {show('paused')}
	public void doStopped() {show('stopped')}

	public static main(args){
		def main = new ScxmlDemo()
		main.execute()
	}
}

Example run:

groovy src\ScxmlDemo.groovy
I'm 'reset' ...
I'm 'running' ...
Now state is: running

Summary
Presented was an example of implementing the Watch example application using the Groovy programming library. The Groovy MarkupBuilder gives a “better” language then XML for creating the state machine document.

SCXML can be the basis of a State-Oriented Programming approach.

Updates

Further Reading

 


My Funny Valentine / Bill Evans & Jim Hall


Follow

Get every new post delivered to your Inbox.