官网:http://commons.apache.org/proper/commons-scxml/index.html
SCXML W3C规范:http://www.w3.org/TR/scxml/
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ctrip.infosec.offline</groupId>
<artifactId>fsm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- Add remote repositories -->
<repositories>
<repository>
<id>Java.Net</id>
<url>http://download.java.net/maven/2/</url>
</repository>
<repository>
<id>JBoss repository</id>
<url>http://repository.jboss.com/maven2/</url>
</repository>
<repository>
<id>maven</id>
<url>http://repo1.maven.org/maven2/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>commons-scxml</groupId>
<artifactId>commons-scxml</artifactId>
<version>0.9</version>
</dependency>
<dependency>
<groupId>commons-jexl</groupId>
<artifactId>commons-jexl</artifactId>
<version>20040901.055348</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.2.2</version>
<!-- <scope>provided</scope> -->
</dependency>
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
myfsm.xml
<?xml version="1.0"?>
<!-- * Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with *
this work for additional information regarding copyright ownership. * The
ASF licenses this file to You under the Apache License, Version 2.0 * (the
"License"); you may not use this file except in compliance with * the License.
You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0
* * Unless required by applicable law or agreed to in writing, software *
distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the
License for the specific language governing permissions and * limitations
under the License. -->
<scxml xmlns="http://www.w3.org/2005/07/scxml" xmlns:my="http://my.custom-actions.domain/custom1"
version="1.0" initial="reset">
<!-- <parallel id="oven"> -->
<datamodel>
<data id="ccxmlid" expr="32459" />
<data id="v3id" expr="17620" />
<data id="dest" expr="'tel:+18315552020'" />
<data id="src" expr="'helloworld2.vxml'" />
<data id="id" expr="'HelloWorld'" />
</datamodel>
<state id="reset">
<transition event="watch.start" target="running" />
</state>
<state id="running">
<transition event="watch.split" target="paused">
<!-- <my:hello name="world" /> -->
</transition>
<transition event="watch.stop" target="stopped" />
</state>
<state id="paused">
<transition event="watch.unsplit" target="running" />
<transition event="watch.stop" target="stopped" />
</state>
<state id="stopped">
<!-- <onentry>
<log expr="'enterd state stopped'" />
</onentry> -->
<transition event="watch.reset" target="reset" />
<!-- <transition event="watch.end" target="end" /> -->
</state>
<!-- <final id="end" /> -->
<!-- </parallel> -->
</scxml>
SCXMLEngine.java(状态机引擎,参考AbstractStateMachine类)
package com.ctrip.infosec.offline.fsm;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import org.apache.commons.scxml.SCXMLExecutor;
import org.apache.commons.scxml.SCXMLListener;
import org.apache.commons.scxml.Status;
import org.apache.commons.scxml.TriggerEvent;
import org.apache.commons.scxml.env.SimpleDispatcher;
import org.apache.commons.scxml.env.SimpleErrorHandler;
import org.apache.commons.scxml.env.SimpleErrorReporter;
import org.apache.commons.scxml.env.jexl.JexlContext;
import org.apache.commons.scxml.env.jexl.JexlEvaluator;
import org.apache.commons.scxml.io.SCXMLParser;
import org.apache.commons.scxml.model.CustomAction;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.model.SCXML;
import org.apache.commons.scxml.model.State;
import org.apache.commons.scxml.model.Transition;
import org.apache.commons.scxml.model.TransitionTarget;
import org.xml.sax.SAXException;
public class SCXMLEngine {
private static SCXMLExecutor engine = null;
public static void main(String[] args) {
engine = new SCXMLExecutor(new JexlEvaluator(), new SimpleDispatcher(),
new SimpleErrorReporter());
// (1) Create a list of custom actions, add as many as are needed
List<CustomAction> customActions = new ArrayList<CustomAction>();
CustomAction ca = new CustomAction(
"http://my.custom-actions.domain/custom1", "hello", Hello.class);
customActions.add(ca);
SCXML stateMachine;
try {
stateMachine = SCXMLParser.parse(SCXMLEngine.class.getClassLoader()
.getResource("myfsm.xml"), new SimpleErrorHandler(),
customActions);
TransitionTarget tt = null;
Map targets = stateMachine.getTargets();
tt = (TransitionTarget)targets.get("paused");
stateMachine.setInitialTarget(tt);
engine.setStateMachine(stateMachine);
engine.setSuperStep(true);
engine.setRootContext(new JexlContext());
engine.addListener(stateMachine,
new SCXMLEngine().new EntryListener());
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (ModelException e) {
e.printStackTrace();
}
try {
engine.go();
} catch (ModelException me) {
me.printStackTrace();
}
String event = null;
Scanner input = new Scanner(System.in);
while (true) {
event = input.nextLine();
if (event.trim() != null && !event.trim().equals("")) {
if (event.equals("exit")) {
input.close();
break;
} else {
TriggerEvent[] evts = { new TriggerEvent(event,
TriggerEvent.SIGNAL_EVENT, null) };
try {
engine.triggerEvents(evts);
Status currStatus = engine.getCurrentStatus();
Set states = currStatus.getStates();
for(Object object : states) {
State state = ((State)object);
System.out.println("current status id is : " + state.getId());
/*if (((State)object).getId().equals("reset")) {
TransitionTarget parent = state.getParent();
System.out.println("parent is : " + parent.getId());
}*/
}
} catch (ModelException me) {
me.printStackTrace();
}
}
}
}
}
/**
* Invoke the no argument method with the following name.
*
* @param methodName The method to invoke.
* @return Whether the invoke was successful.
*/
public boolean invoke(final String methodName) {
Class clas = this.getClass();
try {
Method method = clas.getDeclaredMethod(methodName, new Class[0]);
method.invoke(this, new Object[0]);
} catch (SecurityException se) {
System.out.println(se);
return false;
} catch (NoSuchMethodException nsme) {
System.out.println(nsme);
return false;
} catch (IllegalArgumentException iae) {
System.out.println(iae);
return false;
} catch (IllegalAccessException iae) {
System.out.println(iae);
return false;
} catch (InvocationTargetException ite) {
System.out.println(ite);
return false;
}
return true;
}
public void reset() {
System.out.println("reset method called");
}
public void running() {
System.out.println("running method called");
}
public void paused() {
System.out.println("paused method called");
}
public void stopped() {
System.out.println("stopped method called");
}
/**
* A SCXMLListener that is only concerned about "onentry"
* notifications.
*/
protected class EntryListener implements SCXMLListener {
public void onEntry(final TransitionTarget entered) {
System.out.println("Entering State : " + entered.getId() + ", begin to invoke method " + entered.getId());
invoke(entered.getId());
}
public void onTransition(final TransitionTarget from,
final TransitionTarget to, final Transition transition) {
System.out.println("Transiting from " + from.getId() + " to "
+ to.getId());
}
public void onExit(final TransitionTarget exited) {
System.out.println("Exiting :" + exited.getId());
}
}
}
Hello.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ctrip.infosec.offline.fsm;
import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.scxml.ErrorReporter;
import org.apache.commons.scxml.EventDispatcher;
import org.apache.commons.scxml.SCInstance;
import org.apache.commons.scxml.SCXMLExpressionException;
import org.apache.commons.scxml.TriggerEvent;
import org.apache.commons.scxml.model.ModelException;
import org.apache.commons.scxml.model.Action;
/**
* Our custom "hello world" action.
*/
public class Hello extends Action {
/** Serial version UID. */
private static final long serialVersionUID = 1L;
/** This is who we say hello to. */
private String name;
/** We count callbacks to execute() as part of the test suite. */
public static int callbacks = 0;
/** Public constructor is needed for the I in SCXML IO. */
public Hello() {
super();
}
/**
* Get the name.
*
* @return Returns the name.
*/
public String getName() {
return name;
}
/**
* Set the name.
*
* @param name
* The name to set.
*/
public void setName(String name) {
this.name = name;
}
/*@Override
public void execute(ActionExecutionContext exctx) throws ModelException,
SCXMLExpressionException {
if (exctx.getAppLog().isInfoEnabled()) {
exctx.getAppLog().info("Hello " + name);
}
// For derived events payload testing
TriggerEvent event = new TriggerEvent("helloevent",
TriggerEvent.SIGNAL_EVENT, name);
exctx.getInternalIOProcessor().addEvent(event);
callbacks++;
}*/
@Override
public void execute(EventDispatcher evtDispatcher, ErrorReporter errRep,
SCInstance scInstance, Log appLog, Collection derivedEvents)
throws ModelException, SCXMLExpressionException {
/*if (appLog.isInfoEnabled()) {
appLog.info("Hello " + name);
}*/
System.out.println("Hello " + name);
// For derived events payload testing
TriggerEvent event = new TriggerEvent("watch.test",
TriggerEvent.SIGNAL_EVENT, name);
derivedEvents.add(event);
callbacks++;
System.out.println("callbacks=" + callbacks);
}
}
此外,官网还有一个StopWatch的usercase