Spring State Machine (#1493)

* Neo4j cleanup

* Neo4j cleanup

* Neo4j cleanup x2

* State Machine Init

* cleanup

* White background, Java Util Logging

* Change to Logging

* Static import of asserts.
rename test methods

* fix attempt for S3 -> S4

* Remove awaitility.  add teardown

* fix attempt for S3 -> S4

* fix attempt for S3 -> S4 Final
This commit is contained in:
Danil Kornishev 2017-03-27 15:21:03 -04:00 committed by Zeger Hendrikse
parent 365d75a8d7
commit 1c1c557a39
20 changed files with 814 additions and 0 deletions

View File

@ -189,6 +189,7 @@
<module>spring-sleuth</module> <module>spring-sleuth</module>
<module>spring-social-login</module> <module>spring-social-login</module>
<module>spring-spel</module> <module>spring-spel</module>
<module>spring-state-machine</module>
<module>spring-thymeleaf</module> <module>spring-thymeleaf</module>
<module>spring-userservice</module> <module>spring-userservice</module>
<module>spring-zuul</module> <module>spring-zuul</module>

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1489770331405" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="Start">
<messageEventDefinition id="_2_ED_1"/>
</startEvent>
<parallelGateway gatewayDirection="Unspecified" id="_3" name="Fork"/>
<userTask activiti:exclusive="true" id="_4" name="UserTask"/>
<scriptTask activiti:exclusive="true" id="_5" name="ScriptTask"/>
<endEvent id="_7" name="EndEvent">
<terminateEventDefinition id="_7_ED_1"/>
<extensionElements>
<activiti:executionListener event="start">
<activiti:field>
<activiti:string/>
</activiti:field>
</activiti:executionListener>
<activiti:executionListener event="start"/>
<activiti:executionListener event="start"/>
<activiti:executionListener event="start"/>
<activiti:executionListener event="start"/>
</extensionElements>
</endEvent>
<inclusiveGateway gatewayDirection="Unspecified" id="_6" name="Join"/>
<sequenceFlow id="_8" sourceRef="_3" targetRef="_4"/>
<sequenceFlow id="_9" sourceRef="_3" targetRef="_5"/>
<sequenceFlow id="_10" sourceRef="_5" targetRef="_6"/>
<sequenceFlow id="_11" sourceRef="_4" targetRef="_6"/>
<sequenceFlow id="_12" sourceRef="_6" targetRef="_7"/>
<sequenceFlow id="_13" sourceRef="_2" targetRef="_3"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="myProcess_1">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="35.0" y="95.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="32.0" width="32.0" x="140.0" y="95.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="55.0" width="85.0" x="230.0" y="20.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="230.0" y="180.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7">
<omgdc:Bounds height="32.0" width="32.0" x="475.0" y="130.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_6" id="Shape-_6">
<omgdc:Bounds height="32.0" width="32.0" x="375.0" y="130.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_13" id="BPMNEdge__13" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="67.0" y="111.0"/>
<omgdi:waypoint x="140.0" y="111.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_12" id="BPMNEdge__12" sourceElement="_6" targetElement="_7">
<omgdi:waypoint x="407.0" y="146.0"/>
<omgdi:waypoint x="475.0" y="146.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_3" targetElement="_4">
<omgdi:waypoint x="172.0" y="111.0"/>
<omgdi:waypoint x="230.0" y="47.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="_3" targetElement="_5">
<omgdi:waypoint x="172.0" y="111.0"/>
<omgdi:waypoint x="230.0" y="207.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_11" id="BPMNEdge__11" sourceElement="_4" targetElement="_6">
<omgdi:waypoint x="315.0" y="45.0"/>
<omgdi:waypoint x="390.0" y="45.0"/>
<omgdi:waypoint x="390.0" y="131.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_10" id="BPMNEdge__10" sourceElement="_5" targetElement="_6">
<omgdi:waypoint x="315.0" y="190.0"/>
<omgdi:waypoint x="355.0" y="190.0"/>
<omgdi:waypoint x="375.0" y="146.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1489503806724" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="SI">
<messageEventDefinition id="_2_ED_1"/>
</startEvent>
<userTask activiti:exclusive="true" id="_3" name="S1"/>
<userTask activiti:exclusive="true" id="_4" name="S2"/>
<endEvent id="_5" name="SF">
<terminateEventDefinition id="_5_ED_1"/>
<extensionElements>
<activiti:executionListener event="start">
<activiti:field>
<activiti:string/>
</activiti:field>
</activiti:executionListener>
<activiti:executionListener event="start"/>
<activiti:executionListener event="start"/>
<activiti:executionListener event="start"/>
<activiti:executionListener event="start"/>
</extensionElements>
</endEvent>
<sequenceFlow id="_6" sourceRef="_2" targetRef="_3"/>
<sequenceFlow id="_7" sourceRef="_3" targetRef="_4"/>
<sequenceFlow id="_8" sourceRef="_4" targetRef="_5"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="myProcess_1">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="85.0" y="95.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="40.0" width="75.0" x="180.0" y="105.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="40.0" width="75.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="40.0" width="70.0" x="320.0" y="105.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="40.0" width="70.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="32.0" width="32.0" x="460.0" y="95.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="117.0" y="111.0"/>
<omgdi:waypoint x="180.0" y="125.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_3" targetElement="_4">
<omgdi:waypoint x="255.0" y="125.0"/>
<omgdi:waypoint x="320.0" y="125.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_4" targetElement="_5">
<omgdi:waypoint x="390.0" y="125.0"/>
<omgdi:waypoint x="460.0" y="111.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>parent-modules</artifactId>
<groupId>com.baeldung</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>baeldung-spring-state-machine</artifactId>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>1.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,5 @@
package com.baeldung.spring.stateMachine.applicationReview;
public enum ApplicationReviewEvents {
APPROVE, REJECT
}

View File

@ -0,0 +1,5 @@
package com.baeldung.spring.stateMachine.applicationReview;
public enum ApplicationReviewStates {
PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED
}

View File

@ -0,0 +1,74 @@
package com.baeldung.spring.stateMachine.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
@Configuration
@EnableStateMachine
public class ForkJoinStateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(new StateMachineListener());
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("SI")
.fork("SFork")
.join("SJoin")
.end("SF")
.and()
.withStates()
.parent("SFork")
.initial("Sub1-1")
.end("Sub1-2")
.and()
.withStates()
.parent("SFork")
.initial("Sub2-1")
.end("Sub2-2");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions.withExternal()
.source("SI").target("SFork").event("E1")
.and().withExternal()
.source("Sub1-1").target("Sub1-2").event("sub1")
.and().withExternal()
.source("Sub2-1").target("Sub2-2").event("sub2")
.and()
.withFork()
.source("SFork")
.target("Sub1-1")
.target("Sub2-1")
.and()
.withJoin()
.source("Sub1-2")
.source("Sub2-2")
.target("SJoin");
}
@Bean
public Guard<String, String> mediumGuard() {
return (ctx) -> false;
}
@Bean
public Guard<String, String> highGuard() {
return (ctx) -> false;
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.spring.stateMachine.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
@Configuration
@EnableStateMachine
public class HierarchicalStateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(new StateMachineListener());
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("SI")
.state("SI")
.end("SF")
.and()
.withStates()
.parent("SI")
.initial("SUB1")
.state("SUB2")
.end("SUBEND");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions.withExternal()
.source("SI").target("SF").event("end")
.and().withExternal()
.source("SUB1").target("SUB2").event("se1")
.and().withExternal()
.source("SUB2").target("SUBEND").event("s-end");
}
}

View File

@ -0,0 +1,60 @@
package com.baeldung.spring.stateMachine.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
@Configuration
@EnableStateMachine
public class JunctionStateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(new StateMachineListener());
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("SI")
.junction("SJ")
.state("high")
.state("medium")
.state("low")
.end("SF");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions.withExternal()
.source("SI").target("SJ").event("E1")
.and()
.withJunction()
.source("SJ")
.first("high", highGuard())
.then("medium", mediumGuard())
.last("low")
.and().withExternal()
.source("low").target("SF").event("end");
}
@Bean
public Guard<String, String> mediumGuard() {
return (ctx) -> false;
}
@Bean
public Guard<String, String> highGuard() {
return (ctx) -> false;
}
}

View File

@ -0,0 +1,53 @@
package com.baeldung.spring.stateMachine.config;
import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents;
import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
import java.util.Arrays;
import java.util.HashSet;
@Configuration
@EnableStateMachine
public class SimpleEnumStateMachineConfiguration extends StateMachineConfigurerAdapter<ApplicationReviewStates, ApplicationReviewEvents> {
@Override
public void configure(StateMachineConfigurationConfigurer<ApplicationReviewStates, ApplicationReviewEvents> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(new StateMachineListener());
}
@Override
public void configure(StateMachineStateConfigurer<ApplicationReviewStates, ApplicationReviewEvents> states) throws Exception {
states
.withStates()
.initial(ApplicationReviewStates.PEER_REVIEW)
.state(ApplicationReviewStates.PRINCIPAL_REVIEW)
.end(ApplicationReviewStates.APPROVED)
.end(ApplicationReviewStates.REJECTED);
}
@Override
public void configure(StateMachineTransitionConfigurer<ApplicationReviewStates, ApplicationReviewEvents> transitions) throws Exception {
transitions.withExternal()
.source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.PRINCIPAL_REVIEW).event(ApplicationReviewEvents.APPROVE)
.and().withExternal()
.source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.APPROVED).event(ApplicationReviewEvents.APPROVE)
.and().withExternal()
.source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT)
.and().withExternal()
.source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT);
}
}

View File

@ -0,0 +1,105 @@
package com.baeldung.spring.stateMachine.config;
import java.util.Arrays;
import java.util.HashSet;
import java.util.logging.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
@Configuration
@EnableStateMachine
public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {
public static final Logger LOGGER = Logger.getLogger(SimpleStateMachineConfiguration.class.getName());
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(new StateMachineListener());
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("SI")
.end("SF")
.states(new HashSet<>(Arrays.asList("S1", "S2")))
.state("S4", executeAction(), errorAction())
.stateEntry("S3", entryAction())
.stateDo("S3", executeAction())
.stateExit("S3", exitAction());
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions.withExternal()
.source("SI").target("S1").event("E1").action(initAction())
.and().withExternal()
.source("S1").target("S2").event("E2")
.and().withExternal()
.source("SI").target("S3").event("E3")
.and().withExternal()
.source("S3").target("S4").event("E4").and().withExternal().source("S4").target("SF").event("end").guard(simpleGuard())
.and().withExternal()
.source("S2").target("SF").event("end");
}
@Bean
public Guard<String, String> simpleGuard() {
return (ctx) -> {
int approvalCount = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0);
return approvalCount > 0;
};
}
@Bean
public Action<String, String> entryAction() {
return (ctx) -> {
LOGGER.info("Entry " + ctx.getTarget().getId());
};
}
@Bean
public Action<String, String> executeAction() {
return (ctx) -> {
LOGGER.info("Do " + ctx.getTarget().getId());
int approvals = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0);
approvals++;
ctx.getExtendedState().getVariables().put("approvalCount", approvals);
};
}
@Bean
public Action<String, String> exitAction() {
return (ctx) -> {
LOGGER.info("Exit " + ctx.getSource().getId() + " -> " + ctx.getTarget().getId());
};
}
@Bean
public Action<String, String> errorAction() {
return (ctx) -> {
LOGGER.info("Error " + ctx.getSource().getId() + ctx.getException());
};
}
@Bean
public Action<String, String> initAction() {
return (ctx) -> {
LOGGER.info(ctx.getTarget().getId());
};
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.spring.stateMachine.config;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import java.util.logging.Logger;
public class StateMachineListener extends StateMachineListenerAdapter {
public static final Logger LOGGER = Logger.getLogger(StateMachineListener.class.getName());
@Override
public void stateChanged(State from, State to) {
LOGGER.info(String.format("Transitioned from %s to %s%n", from == null ? "none" : from.getId(), to.getId()));
}
}

View File

@ -0,0 +1,45 @@
package com.baeldung.spring.stateMachine;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.statemachine.StateMachine;
import com.baeldung.spring.stateMachine.config.ForkJoinStateMachineConfiguration;
public class ForkJoinStateMachineTest {
@Test
public void whenForkStateEntered_thenMultipleSubStatesEntered() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class);
StateMachine stateMachine = ctx.getBean(StateMachine.class);
stateMachine.start();
boolean success = stateMachine.sendEvent("E1");
assertTrue(success);
assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds()));
}
@Test
public void whenAllConfiguredJoinEntryStatesAreEntered_thenTransitionToJoinState() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class);
StateMachine stateMachine = ctx.getBean(StateMachine.class);
stateMachine.start();
boolean success = stateMachine.sendEvent("E1");
assertTrue(success);
assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds()));
assertTrue(stateMachine.sendEvent("sub1"));
assertTrue(stateMachine.sendEvent("sub2"));
assertEquals("SJoin", stateMachine.getState().getId());
}
}

View File

@ -0,0 +1,37 @@
package com.baeldung.spring.stateMachine;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.statemachine.StateMachine;
import com.baeldung.spring.stateMachine.config.HierarchicalStateMachineConfiguration;
public class HierarchicalStateMachineTest {
@Test
public void whenTransitionToSubMachine_thenSubStateIsEntered() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(HierarchicalStateMachineConfiguration.class);
StateMachine stateMachine = ctx.getBean(StateMachine.class);
stateMachine.start();
assertEquals(Arrays.asList("SI", "SUB1"), stateMachine.getState().getIds());
stateMachine.sendEvent("se1");
assertEquals(Arrays.asList("SI", "SUB2"), stateMachine.getState().getIds());
stateMachine.sendEvent("s-end");
assertEquals(Arrays.asList("SI", "SUBEND"), stateMachine.getState().getIds());
stateMachine.sendEvent("end");
assertEquals(1, stateMachine.getState().getIds().size());
assertEquals("SF", stateMachine.getState().getId());
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.spring.stateMachine;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.statemachine.StateMachine;
import com.baeldung.spring.stateMachine.config.JunctionStateMachineConfiguration;
public class JunctionStateMachineTest {
@Test
public void whenTransitioningToJunction_thenArriveAtSubJunctionNode() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JunctionStateMachineConfiguration.class);
StateMachine stateMachine = ctx.getBean(StateMachine.class);
stateMachine.start();
stateMachine.sendEvent("E1");
Assert.assertEquals("low", stateMachine.getState().getId());
stateMachine.sendEvent("end");
Assert.assertEquals("SF", stateMachine.getState().getId());
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.spring.stateMachine;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.statemachine.StateMachine;
import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents;
import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates;
import com.baeldung.spring.stateMachine.config.SimpleEnumStateMachineConfiguration;
public class StateEnumMachineTest {
private StateMachine stateMachine;
@Before
public void setUp() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleEnumStateMachineConfiguration.class);
stateMachine = ctx.getBean(StateMachine.class);
stateMachine.start();
}
@Test
public void whenStateMachineConfiguredWithEnums_thenStateMachineAcceptsEnumEvents() {
assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.APPROVE));
assertEquals(ApplicationReviewStates.PRINCIPAL_REVIEW, stateMachine.getState().getId());
assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.REJECT));
assertEquals(ApplicationReviewStates.REJECTED, stateMachine.getState().getId());
}
}

View File

@ -0,0 +1,35 @@
package com.baeldung.spring.stateMachine;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineBuilder;
public class StateMachineBuilderTest {
@Test
public void whenUseStateMachineBuilder_thenBuildSuccessAndMachineWorks() throws Exception {
StateMachineBuilder.Builder<String, String> builder = StateMachineBuilder.builder();
builder.configureStates().withStates()
.initial("SI")
.state("S1")
.end("SF");
builder.configureTransitions()
.withExternal()
.source("SI").target("S1").event("E1")
.and().withExternal()
.source("S1").target("SF").event("E2");
StateMachine machine = builder.build();
machine.start();
machine.sendEvent("E1");
assertEquals("S1", machine.getState().getId());
machine.sendEvent("E2");
assertEquals("SF", machine.getState().getId());
}
}

View File

@ -0,0 +1,51 @@
package com.baeldung.spring.stateMachine;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.statemachine.StateMachine;
import com.baeldung.spring.stateMachine.config.SimpleStateMachineConfiguration;
public class StateMachineTest {
private AnnotationConfigApplicationContext ctx;
private StateMachine stateMachine;
@Before
public void setUp() {
ctx = new AnnotationConfigApplicationContext(SimpleStateMachineConfiguration.class);
stateMachine = ctx.getBean(StateMachine.class);
stateMachine.start();
}
@Test
public void whenSimpleStringStateMachineEvents_thenEndState() {
assertEquals("SI", stateMachine.getState().getId());
stateMachine.sendEvent("E1");
assertEquals("S1", stateMachine.getState().getId());
stateMachine.sendEvent("E2");
assertEquals("S2", stateMachine.getState().getId());
}
@Test
public void whenSimpleStringMachineActionState_thenActionExecuted() {
stateMachine.sendEvent("E3");
assertEquals("S3", stateMachine.getState().getId());
boolean acceptedE4 = stateMachine.sendEvent("E4");
assertTrue(acceptedE4);
assertEquals("S4", stateMachine.getState().getId());
assertEquals(2, stateMachine.getExtendedState().getVariables().get("approvalCount"));
stateMachine.sendEvent("end");
assertEquals("SF", stateMachine.getState().getId());
}
}