diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index cc67587f..49f21ce4 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -500,22 +500,25 @@ all extended state variables(T1, T2 and T3) are TRUE. include::samples/demo/tasks/Application.java[tags=snippetAC] ---- +Actions below will simply send event to a state machine to request +next step which would be either fallback or continue back to ready. + [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetAD] ---- +Currently default region execution is synchronous but it can be +changed to asynchronous by changing `TaskExecutor`. Task will simulate +work by sleeping 2 seconds so you'll able to see how actions in +regions are executed parallel. + [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetAE] ---- -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetAF] ----- - -Lets see an example how this state machine actually works. +Lets see an examples how this state machine actually works. [source,text] ---- @@ -525,12 +528,44 @@ Entry state READY sm>tasks run Entry state TASKS -run task on T1 -run task on T2 run task on T3 -run task on T1 done +run task on T2 +run task on T1 run task on T2 done +run task on T1 done run task on T3 done +Entry state T2 +Entry state T3 +Entry state T1 +Entry state T1E +Entry state T2E +Entry state T3E +Exit state TASKS +Entry state JOIN +Exit state JOIN +Entry state READY +---- + +In above we can execute tasks multiple times. + +[source,text] +---- +sm>tasks list +Tasks {T1=true, T3=true, T2=true} + +sm>tasks fail T1 + +sm>tasks list +Tasks {T1=false, T3=true, T2=true} + +sm>tasks run +Entry state TASKS +run task on T1 +run task on T3 +run task on T2 +run task on T1 done +run task on T3 done +run task on T2 done Entry state T1 Entry state T3 Entry state T2 @@ -540,11 +575,55 @@ Entry state T3E Exit state TASKS Entry state JOIN Exit state JOIN +Entry state ERROR +Entry state AUTOMATIC +Exit state AUTOMATIC +Exit state ERROR Entry state READY - -sm> ---- +In above, if we simulate failure for task T1, it is fixed +automatically. + +[source,text] +---- +sm>tasks list +Tasks {T1=true, T3=true, T2=true} + +sm>tasks fail T2 + +sm>tasks run +Entry state TASKS +run task on T2 +run task on T1 +run task on T3 +run task on T2 done +run task on T1 done +run task on T3 done +Entry state T2 +Entry state T1 +Entry state T3 +Entry state T1E +Entry state T2E +Entry state T3E +Exit state TASKS +Entry state JOIN +Exit state JOIN +Entry state ERROR +Entry state AUTOMATIC +Exit state AUTOMATIC +Entry state MANUAL + +sm>tasks fix +Exit state MANUAL +Exit state ERROR +Entry state READY +---- + +In above if we simulate failure for either task T2 or T3, state +machine goes to MANUAL state where problem needs to be fixed manually +before we're able to go back to READY state. + == Washer Washer is a sample demonstrating a use of a history state to recover a diff --git a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java index c65e0fc2..b83f8343 100644 --- a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java +++ b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Application.java @@ -34,6 +34,7 @@ import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.guard.Guard; +import org.springframework.util.ObjectUtils; @Configuration public class Application { @@ -122,7 +123,6 @@ public class Application { .withInternal() .source(States.MANUAL) .action(fixAction()) - .state(States.ERROR) .event(Events.FIX); } //end::snippetAB[] @@ -135,8 +135,9 @@ public class Application { @Override public boolean evaluate(StateContext context) { Map variables = context.getExtendedState().getVariables(); - return !(variables.get("T1").equals(true) && variables.get("T2").equals(true) && variables - .get("T3").equals(true)); + return !(ObjectUtils.nullSafeEquals(variables.get("T1"), true) + && ObjectUtils.nullSafeEquals(variables.get("T2"), true) + && ObjectUtils.nullSafeEquals(variables.get("T3"), true)); } }; } @@ -150,7 +151,9 @@ public class Application { @Override public void execute(StateContext context) { Map variables = context.getExtendedState().getVariables(); - if (variables.get("T1").equals(true)) { + if (ObjectUtils.nullSafeEquals(variables.get("T1"), true) + && ObjectUtils.nullSafeEquals(variables.get("T2"), true) + && ObjectUtils.nullSafeEquals(variables.get("T3"), true)) { context.getStateMachine().sendEvent(Events.CONTINUE); } else { context.getStateMachine().sendEvent(Events.FALLBACK); @@ -166,30 +169,28 @@ public class Application { @Override public void execute(StateContext context) { Map variables = context.getExtendedState().getVariables(); - if (variables.get("T1").equals(true) && variables.get("T2").equals(true) - && variables.get("T3").equals(true)) { - context.getStateMachine().sendEvent(Events.CONTINUE); - } + variables.put("T1", true); + variables.put("T2", true); + variables.put("T3", true); + context.getStateMachine().sendEvent(Events.CONTINUE); } }; } //end::snippetAD[] -//tag::snippetAE[] @Bean public Tasks tasks() { return new Tasks(); } -//end::snippetAE[] -//tag::snippetAF[] +//tag::snippetAE[] @Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(5); return taskExecutor; } -//end::snippetAF[] +//end::snippetAE[] } diff --git a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Tasks.java b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Tasks.java index 347f544b..4798b1dc 100644 --- a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Tasks.java +++ b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/Tasks.java @@ -49,14 +49,10 @@ public class Tasks { stateMachine.sendEvent(Events.RUN); } - public void cont() { - stateMachine.sendEvent(Events.CONTINUE); - } - - public void fix(String task) { - if (tasks.containsKey(task)) { - tasks.put(task, true); - } + public void fix() { + tasks.put("T1", true); + tasks.put("T2", true); + tasks.put("T3", true); stateMachine.sendEvent(Events.FIX); } @@ -68,44 +64,33 @@ public class Tasks { @StatesOnTransition(target = States.T1) public void taskT1(ExtendedState extendedState) { - log.info("run task on T1"); - sleep(2000); - extendedState.getVariables().put("T1", tasks.get("T1")); - log.info("run task on T1 done"); + runTask("T1", extendedState); } @StatesOnTransition(target = States.T2) public void taskT2(ExtendedState extendedState) { - log.info("run task on T2"); - sleep(2000); - extendedState.getVariables().put("T2", tasks.get("T2")); - log.info("run task on T2 done"); + runTask("T2", extendedState); } @StatesOnTransition(target = States.T3) public void taskT3(ExtendedState extendedState) { - log.info("run task on T3"); - sleep(2000); - extendedState.getVariables().put("T3", tasks.get("T3")); - log.info("run task on T3 done"); + runTask("T3", extendedState); } +//tag::snippetA[] @StatesOnTransition(target = States.AUTOMATIC) public void automaticFix(ExtendedState extendedState) { Map variables = extendedState.getVariables(); - if (variables.get("T1").equals(false)) { - variables.put("T1", true); - tasks.put("T1", true); - } + variables.put("T1", true); + tasks.put("T1", true); } +//end::snippetA[] - @StatesOnTransition(target = States.MANUAL) - public void manualFix(ExtendedState extendedState) { - Map variables = extendedState.getVariables(); - if (variables.get("T2").equals(false)) { - variables.put("T2", true); - tasks.put("T2", true); - } + private void runTask(String task, ExtendedState extendedState) { + log.info("run task on " + task); + sleep(2000); + extendedState.getVariables().put(task, tasks.get(task)); + log.info("run task on " + task + " done"); } private static void sleep(long millis) { diff --git a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java index ff9d755f..78b7240b 100644 --- a/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java +++ b/spring-statemachine-samples/tasks/src/main/java/demo/tasks/TasksCommands.java @@ -37,9 +37,9 @@ public class TasksCommands implements CommandMarker { return tasks.toString(); } - @CliCommand(value = "tasks fix", help = "Fix task") - public void fix(@CliOption(key = {"", "task"}, help = "Task id") String task) { - tasks.fix(task); + @CliCommand(value = "tasks fix", help = "Fix tasks") + public void fix() { + tasks.fix(); } @CliCommand(value = "tasks fail", help = "Fail task") diff --git a/spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java b/spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java index 82fe0f7d..e39eaf5f 100644 --- a/spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java +++ b/spring-statemachine-samples/tasks/src/test/java/demo/tasks/TasksTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -54,9 +55,8 @@ public class TasksTests { @Test public void testInitialState() throws InterruptedException { - assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS), is(true)); - assertThat(listener.stateChangedCount, is(1)); - assertThat(machine.getState().getIds(), contains(States.READY)); + Map variables = machine.getExtendedState().getVariables(); + assertThat(variables.size(), is(0)); } @Test @@ -65,6 +65,8 @@ public class TasksTests { tasks.run(); assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS), is(true)); assertThat(machine.getState().getIds(), contains(States.READY)); + Map variables = machine.getExtendedState().getVariables(); + assertThat(variables.size(), is(3)); } @Test @@ -74,10 +76,16 @@ public class TasksTests { assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS), is(true)); assertThat(machine.getState().getIds(), contains(States.READY)); + Map variables = machine.getExtendedState().getVariables(); + assertThat(variables.size(), is(3)); + listener.reset(9, 0, 0); tasks.run(); assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS), is(true)); assertThat(machine.getState().getIds(), contains(States.READY)); + + variables = machine.getExtendedState().getVariables(); + assertThat(variables.size(), is(3)); } @Test @@ -92,11 +100,17 @@ public class TasksTests { @Test public void testFailManualFix() throws InterruptedException { - listener.reset(9, 0, 0); + listener.reset(11, 0, 0); tasks.fail("T2"); tasks.run(); - tasks.fix("T2"); - tasks.cont(); + assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS), is(true)); + + Map variables = machine.getExtendedState().getVariables(); + assertThat(variables.size(), is(3)); + + assertThat(machine.getState().getIds(), contains(States.ERROR, States.MANUAL)); + listener.reset(1, 0, 0); + tasks.fix(); assertThat(listener.stateChangedLatch.await(6, TimeUnit.SECONDS), is(true)); assertThat(machine.getState().getIds(), contains(States.READY)); } @@ -111,8 +125,9 @@ public class TasksTests { tasks = context.getBean(Tasks.class); listener = context.getBean(TestListener.class); machine.start(); - // lets do a little sleep to wait sm to start - Thread.sleep(1000); + assertThat(listener.stateChangedLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(listener.stateChangedCount, is(1)); + assertThat(machine.getState().getIds(), contains(States.READY)); } @After @@ -152,8 +167,8 @@ public class TasksTests { @Override public void stateChanged(State from, State to) { - stateChangedLatch.countDown(); stateChangedCount++; + stateChangedLatch.countDown(); } @Override @@ -170,8 +185,8 @@ public class TasksTests { @Override public void transitionEnded(Transition transition) { - transitionLatch.countDown(); transitionCount++; + transitionLatch.countDown(); } public void reset(int c1, int c2, int c3) {