I have a statemachine defined with two fork/joins and with each of those submachines having their own fork/join. I have tested that all states correctly transition including all sub states and fork/joins all the way to the final end state. That is great!, however, I discovered two problems related to actions on sub-submachines (2 levels down):
1) Transitioning within a sub-submachine (2 levels down) region never invokes the exit action (but mysteriously, they all invoke their entry actions)
2) Transitioning out of a join from the sub-submachine into another state results in a StateContext containing no useful information on what state is involved (I assume this is because its coming from a join, but I though an entry Action would at least have the target non-null).
Full example showing this issue (will echo entry and exit, as well as a tests on expected states entered/exited):
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.config.StateMachineBuilder.Builder;
public class MissingActionExecution {
private List<String> statesEntered_;
private List<String> statesExited_;
public void before()
{
statesEntered_ = new ArrayList<String>();
statesExited_ = new ArrayList<String>();
}
public static void main(String[] args) throws Exception {
MissingActionExecution missingActions = new MissingActionExecution();
missingActions.before();
missingActions.run();
}
public void run() throws Exception
{
Builder<String,String> builder = StateMachineBuilder.builder();
builder.configureStates().withStates().initial("startState")
.fork("forkState")
.state("state2")
.join("joinState")
.state("state3", new EchoEntryAction<String,String>("state3"), new EchoExitAction<String,String>("state3"))
.end("endState")
.and()
.withStates()
.parent("state2")
.initial("state21SubStart")
.state("state21SubStateA", new EchoEntryAction<String,String>("state21SubStateA"), new EchoExitAction<String,String>("state21SubStateA"))
.fork("forkInState21")
.state("state21SubStateB")
.join("joinInState21")
.state("state21SubStateC", new EchoEntryAction<String,String>("state21SubStateC"), new EchoExitAction<String,String>("state21SubStateC"))
.end("state21SubEnd")
.and()
.withStates()
.parent("state2")
.initial("state22SubStart")
.state("state22SubStateA", new EchoEntryAction<String,String>("state22SubStateA"), new EchoExitAction<String,String>("state22SubStateA"))
.fork("forkInState22")
.state("state22SubStateB")
.join("joinInState22")
.state("state22SubStateC", new EchoEntryAction<String,String>("state22SubStateC"), new EchoExitAction<String,String>("state22SubStateC"))
.end("state22SubEnd")
.and()
.withStates()
.parent("state21SubStateB")
.initial("state21SubStateBStart")
.state("state21SubStateBA", new EchoEntryAction<String,String>("state21SubStateBA"), new EchoExitAction<String,String>("state21SubStateBA"))
.state("state21SubStateBB", new EchoEntryAction<String,String>("state21SubStateBB"), new EchoExitAction<String,String>("state21SubStateBB"))
.state("state21SubStateBC", new EchoEntryAction<String,String>("state21SubStateBC"), new EchoExitAction<String,String>("state21SubStateBC"))
.end("state21SubStateBEnd")
.and()
.withStates()
.parent("state21SubStateB")
.initial("state211SubStateBStart")
.state("state211SubStateBA", new EchoEntryAction<String,String>("state211SubStateBA"), new EchoExitAction<String,String>("state211SubStateBA"))
.state("state211SubStateBB", new EchoEntryAction<String,String>("state211SubStateBB"), new EchoExitAction<String,String>("state211SubStateBB"))
.state("state211SubStateBC", new EchoEntryAction<String,String>("state211SubStateBC"), new EchoExitAction<String,String>("state211SubStateBC"))
.end("state211SubStateBEnd")
.and()
.withStates()
.parent("state22SubStateB")
.initial("state22SubStateBStart")
.state("state22SubStateBA", new EchoEntryAction<String,String>("state22SubStateBA"), new EchoExitAction<String,String>("state22SubStateBA"))
.state("state22SubStateBB", new EchoEntryAction<String,String>("state22SubStateBB"), new EchoExitAction<String,String>("state22SubStateBB"))
.state("state22SubStateBC", new EchoEntryAction<String,String>("state22SubStateBC"), new EchoExitAction<String,String>("state22SubStateBC"))
.end("state22SubStateBEnd")
.and()
.withStates()
.parent("state22SubStateB")
.initial("state222SubStateBStart")
.state("state222SubStateBA", new EchoEntryAction<String,String>("state222SubStateBA"), new EchoExitAction<String,String>("state222SubStateBA"))
.state("state222SubStateBB", new EchoEntryAction<String,String>("state222SubStateBB"), new EchoExitAction<String,String>("state222SubStateBB"))
.state("state222SubStateBC", new EchoEntryAction<String,String>("state222SubStateBC"), new EchoExitAction<String,String>("state222SubStateBC"))
.end("state222SubStateBEnd");
builder.configureTransitions()
.withExternal().source("startState").target("state2").and()
.withFork().source("forkState").target("state21SubStart").target("state22SubStart").and()
.withExternal().source("state21SubStart").target("state21SubStateA").and()
.withExternal().source("state21SubStateA").target("state21SubStateB").and()
.withFork().source("forkInState21").target("state21SubStateBStart").target("state211SubStateBStart").and()
.withExternal().source("state21SubStateBStart").target("state21SubStateBA").and()
.withExternal().source("state21SubStateBA").target("state21SubStateBB").and()
.withExternal().source("state21SubStateBB").target("state21SubStateBC").and()
.withExternal().source("state21SubStateBC").target("state21SubStateBEnd").and()
.withExternal().source("state211SubStateBStart").target("state211SubStateBA").and()
.withExternal().source("state211SubStateBA").target("state211SubStateBB").and()
.withExternal().source("state211SubStateBB").target("state211SubStateBC").and()
.withExternal().source("state211SubStateBC").target("state211SubStateBEnd").and()
.withJoin().source("state21SubStateBEnd").source("state211SubStateBEnd").target("joinInState21").and()
.withExternal().source("joinInState21").target("state21SubStateC").and()
.withExternal().source("state21SubStateC").target("state21SubEnd").and()
.withExternal().source("state22SubStart").target("state22SubStateA").and()
.withExternal().source("state22SubStateA").target("state22SubStateB").and()
.withFork().source("forkInState22").target("state22SubStateBStart").target("state222SubStateBStart").and()
.withExternal().source("state22SubStateBStart").target("state22SubStateBA").and()
.withExternal().source("state22SubStateBA").target("state22SubStateBB").and()
.withExternal().source("state22SubStateBB").target("state22SubStateBC").and()
.withExternal().source("state22SubStateBC").target("state22SubStateBEnd").and()
.withExternal().source("state222SubStateBStart").target("state222SubStateBA").and()
.withExternal().source("state222SubStateBA").target("state222SubStateBB").and()
.withExternal().source("state222SubStateBB").target("state222SubStateBC").and()
.withExternal().source("state222SubStateBC").target("state222SubStateBEnd").and()
.withJoin().source("state22SubStateBEnd").source("state222SubStateBEnd").target("joinInState22").and()
.withExternal().source("joinInState22").target("state22SubStateC").and()
.withExternal().source("state22SubStateC").target("state22SubEnd").and()
.withJoin().source("state21SubEnd").source("state22SubEnd").target("joinState").and()
.withExternal().source("joinState").target("state3").and()
.withExternal().source("state3").target("endState");
StateMachine<String,String> sm = builder.build();
sm.start();
System.out.println("End State is:" +sm.getState().getIds() + "\n");
Set<String> expected = new HashSet<String>(Arrays.asList(new String[]{
"state21SubStateA",
"state21SubStateBA","state21SubStateBB","state21SubStateBC",
"state211SubStateBA","state211SubStateBB","state211SubStateBC",
"state21SubStateC",
"state22SubStateA",
"state22SubStateBA","state22SubStateBB","state22SubStateBC",
"state222SubStateBA","state222SubStateBB","state222SubStateBC",
"state22SubStateC",
"state3"}));
boolean statesExpectedOnEntry = new HashSet<String>(statesEntered_).equals(expected);
if (!statesExpectedOnEntry)
{
System.out.println("ENTRY TEST FAIL: \nfound:\n" + statesEntered_ + "\nexpected: \n" + expected + "\n");
}
boolean statesExpectedOnExit = new HashSet<String>(statesExited_).equals(expected);
if (!statesExpectedOnExit)
{
System.out.println("EXIT TEST FAIL: \nfound:\n" + statesExited_ + "\nexpected: \n" + expected + "\n");
}
sm.stop();
}
private class EchoEntryAction<S, E> implements Action<S, E> {
private String state_;
public EchoEntryAction(String state)
{
state_= state;
}
public void execute(StateContext<S, E> context) {
if (context == null || context.getTarget() == null)
{
System.out.println("Entered NULL TARGET: " + state_);
//statesEntered_.add(state_);
return;
}
statesEntered_.add((String)context.getTarget().getId());
System.out.println("Entered: " + context.getTarget().getId());
}
}
private class EchoExitAction<S, E> implements Action<S, E> {
private String state_;
public EchoExitAction(String state)
{
state_= state;
}
public void execute(StateContext<S, E> context) {
if (context == null || context.getSource() == null)
{
System.out.println("Exited NULL SOURCE: " + state_);
//statesExited_.add(state_);
return;
}
statesExited_.add((String)context.getSource().getId());
System.out.println("Exited: " + context.getSource().getId());
}
}
}
Hi there,
I have a statemachine defined with two fork/joins and with each of those submachines having their own fork/join. I have tested that all states correctly transition including all sub states and fork/joins all the way to the final end state. That is great!, however, I discovered two problems related to actions on sub-submachines (2 levels down):
1) Transitioning within a sub-submachine (2 levels down) region never invokes the exit action (but mysteriously, they all invoke their entry actions)
2) Transitioning out of a join from the sub-submachine into another state results in a StateContext containing no useful information on what state is involved (I assume this is because its coming from a join, but I though an entry Action would at least have the target non-null).
Full example showing this issue (will echo entry and exit, as well as a tests on expected states entered/exited):