diff --git a/.gitignore b/.gitignore index 7b44affb..1100e6c2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build .classpath .project *.iml +*.log *.ipr *.iws metastore_db diff --git a/build.gradle b/build.gradle index 143b1383..86c3f855 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,13 @@ buildscript { classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7") classpath('org.asciidoctor:asciidoctor-gradle-plugin:1.5.2') classpath("io.spring.gradle:docbook-reference-plugin:0.3.0") + classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.1.RELEASE") + } +} + +def sampleProjects() { + subprojects.findAll { project -> + project.name.contains('spring-statemachine-samples') && project.name != 'spring-statemachine-samples-common' } } @@ -104,6 +111,21 @@ project('spring-statemachine-core') { } } +configure(sampleProjects()) { + apply plugin: 'spring-boot' + dependencies { + compile project(":spring-statemachine-samples-common") + } +} + +project('spring-statemachine-samples-common') { + dependencies { + compile project(":spring-statemachine-core") + compile "org.springframework.shell:spring-shell:1.1.0.RELEASE" + compile "org.springframework.boot:spring-boot-starter:1.2.1.RELEASE" + } +} + configure(rootProject) { description = 'Spring State Machine' @@ -152,7 +174,9 @@ configure(rootProject) { task copyDocsSamples(type: Copy) { from 'spring-statemachine-core/src/test/java/org/springframework/statemachine/docs' - include '*.java' + from 'spring-statemachine-samples/src/main/java/' + from 'spring-statemachine-samples/turnstile/src/main/java/' + include '**/*.java' into 'docs/src/reference/asciidoc/samples' } diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc index c269bea6..e828028b 100644 --- a/docs/src/reference/asciidoc/appendix.adoc +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -12,12 +12,12 @@ material in this reference documentation. [source,java,indent=0] ---- -include::samples/MyStates.java[tags=snippetA] +include::samples/States.java[tags=snippetA] ---- [source,java,indent=0] ---- -include::samples/MyEvents.java[tags=snippetA] +include::samples/Events.java[tags=snippetA] ---- [appendix] @@ -28,55 +28,56 @@ This appendix provides generic information about state machines. [glossary] === Glossary -State Machine:: +*State Machine*:: Main entity driving a collection of states together with regions, transitions and events. -State:: +*State*:: A state models a situation during which some invariant condition holds. State is the main entity of a state machine where state changes are driven by an events. -Transition:: +*Transition*:: A transition is a relationship between a source state and a target state. It may be part of a compound transition, which takes the state machine from one state configuration to another, representing the complete response of the state machine to an occurrence of an event of a particular type. -Event:: +*Event*:: An entity which is send to a state machine which then drives a various state changes. -Initial State:: +*Initial State*:: A special state in which the state machine starts. Initial state is always bound to a particulal state machine or a region. A state machine with a multiple regions may have a multiple initial states. -End State:: +*End State*:: Also called as a final state is a special kind of state signifying that the enclosing region is completed. If the enclosing region is directly contained in a state machine and all other regions in the state machine also are completed, then it means that the entire state machine is completed. -Region:: +*Region*:: A region is an orthogonal part of either a composite state or a state machine. It contains states and transitions. -Guard:: +*Guard*:: Is a boolean expression evaluated dynamically based on the value of extended state variables and event parameters. Guard conditions affect the behavior of a state machine by enabling actions or transitions only when they evaluate to TRUE and disabling them when they evaluate to FALSE. -Action:: +*Action*:: A action is a behaviour executed during the triggering of the transition. === A State Machines Crash Course -TBD. +This appendix provides generic crash course to a state machine +concepts. ==== States TBD. diff --git a/docs/src/reference/asciidoc/images/statechart1.png b/docs/src/reference/asciidoc/images/statechart1.png new file mode 100644 index 00000000..2d6e0478 Binary files /dev/null and b/docs/src/reference/asciidoc/images/statechart1.png differ diff --git a/docs/src/reference/asciidoc/introduction.adoc b/docs/src/reference/asciidoc/introduction.adoc index 25b1fce2..835748af 100644 --- a/docs/src/reference/asciidoc/introduction.adoc +++ b/docs/src/reference/asciidoc/introduction.adoc @@ -1,6 +1,9 @@ [[introduction]] = Introduction +Spring State Machine(SSM) is a framework for application developers to +use state machine concepts with Spring. + == Requirements Spring Statemachine {revnumber} is built and tested with JDK 7, Spring diff --git a/docs/src/reference/asciidoc/preface.adoc b/docs/src/reference/asciidoc/preface.adoc index 86c36454..c085ffcd 100644 --- a/docs/src/reference/asciidoc/preface.adoc +++ b/docs/src/reference/asciidoc/preface.adoc @@ -1,8 +1,5 @@ [preface] == Preface -Spring State Machine(SSM) is a framework for application developers to use -state machine concepts with Spring. - Concept of a state machine is most likely older that any of a reader of this reference documentation and definitely older than a Java language itself. Description of finite automate dates back to 1943 diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 829c0cfc..831e7681 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -1,10 +1,77 @@ [[statemachine-examples]] -= Statemachine Examples += State Machine Examples This part of the reference documentation explains the use of state -machines as sample code together with a uml state charts. +machines as sample code together with a uml state charts. We do few +shortcuts when representing relationship between a state chart, SSM +configuration and what an application does with a state machine. For +complete examples go and study the samples repository. -== Simple Statemachine +== Turnstile -TBD +Turnstile is a simple device which gives you an access if payment is +made and is a very simple to model using a state machine. In its +simples form there are only two states, `LOCKED` and `UNLOCKED`. Two +events, `COIN` and `PUSH` can happen if you try to go through it or +you make a payment. + +image::images/statechart1.png[] + +.States +[source,java,indent=0] +---- +include::samples/demo/turnstile/Application.java[tags=snippetB] +---- + +.Events +[source,java,indent=0] +---- +include::samples/demo/turnstile/Application.java[tags=snippetC] +---- + +.Configuration +[source,java,indent=0] +---- +include::samples/demo/turnstile/Application.java[tags=snippetA] +---- + +You can see how this sample state machine interacts with event by +running `turnstile` sample. +[source,text] +---- +$ java -jar spring-statemachine-samples-turnstile-1.0.0.BUILD-SNAPSHOT.jar + +sm>sm print ++----------------------------------------------------------------+ +| SM | ++----------------------------------------------------------------+ +| | +| +----------------+ +----------------+ | +| *-->| LOCKED | | UNLOCKED | | +| +----------------+ +----------------+ | +| +---| entry/ | | entry/ |---+ | +| | | exit/ | | exit/ | | | +| | | | | | | | +| PUSH| | |---COIN-->| | |COIN | +| | | | | | | | +| | | | | | | | +| | | |<--PUSH---| | | | +| +-->| | | |<--+ | +| | | | | | +| +----------------+ +----------------+ | +| | ++----------------------------------------------------------------+ + +sm>sm start +State changed to LOCKED +State machine started + +sm>sm event COIN +State changed to UNLOCKED +Event COIN send + +sm>sm event PUSH +State changed to LOCKED +Event PUSH send +---- diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index c9bb9522..51c5922c 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -6,24 +6,93 @@ that Spring Statemachine provides to any Spring based application. <> describes the generic configuration support. +<> describes the generic state machine factory support. + +<> describes the generic state machine listener support. + +<> describes the generic Spring application context support. + [[sm-config]] == Statemachine Configuration One of the common tasks when using a Statemachine is to design its -runtime configuration. This chapter will focus on How Spring +runtime configuration. This chapter will focus on how Spring Statemachine is configured and how it leverages Spring's lightweight IoC containers to simplify the application internals to make it more manageable. -=== Using the Spring Statemachine JavaConfig +=== Configuring States + +We'll get into more complex configuration examples a bit later but +lets first start with a something simple. For most simple state +machine you [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetA] ---- +=== Configuring Hierarchical States -== Context Ingregration +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetB] +---- + +=== Configuring Transitions + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetC] +---- + +=== Configuring Guards + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetD] +---- + +=== Configuring Actions + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetE] +---- + +[[sm-factories]] +== State Machine Factories +There are use cases when state machine needs to be created dynamically +instead of defining static configuration at compile time. For example +if there are custom components which are using its own state machines +and these components are created dynamically it is impossible to have +a static state machined build during the application start. Internally +state machines are always build via a factory interfaces and this then +gives user an option to use this feature programmatically. +Configuration for state machine factory is exactly same as you've seen +in various examples in this document where state machine configuration +is hard coded. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetF] +---- + +=== Factory Limitations + +xxx + +[[sm-listeners]] +== State Machine Listeners + +There are use cases where you just want to know what is happening with +a state machine, react to something or simply get logging for +debugging purposes. SSM provides interfaces for adding listeners which +then gives an option to get callback when various state changes, +actions, etc are happening. + +[[sm-context]] +== Context Integration TBD @@ -31,3 +100,9 @@ TBD TBD +=== Context Events + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetG] +---- diff --git a/docs/src/statecharts/statechart1.txt b/docs/src/statecharts/statechart1.txt new file mode 100644 index 00000000..a551ef9a --- /dev/null +++ b/docs/src/statecharts/statechart1.txt @@ -0,0 +1,19 @@ ++----------------------------------------------------------------+ +| SM | ++----------------------------------------------------------------+ +| | +| +----------------+ +----------------+ | +| *-->| LOCKED | | UNLOCKED | | +| +----------------+ +----------------+ | +| +---| entry/ | | entry/ |---+ | +| | | exit/ | | exit/ | | | +| | | | | | | | +| PUSH| | |---COIN-->| | |COIN | +| | | | | | | | +| | | | | | | | +| | | |<--PUSH---| | | | +| +-->| | | |<--+ | +| | | | | | +| +----------------+ +----------------+ | +| | ++----------------------------------------------------------------+ diff --git a/settings.gradle b/settings.gradle index 0786e4e6..009551c7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,16 @@ rootProject.name = 'spring-statemachine' include 'spring-statemachine-core' + +include 'spring-statemachine-samples' +include 'spring-statemachine-samples:turnstile' + +rootProject.children.find { + if (it.name == 'spring-statemachine-samples') { + it.name = 'spring-statemachine-samples-common' + it.children.each { + it.name = 'spring-statemachine-samples-' + it.name + } + } +} + diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java index ca043315..80f0e74a 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java @@ -17,11 +17,19 @@ package org.springframework.statemachine.docs; import java.util.EnumSet; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.event.StateMachineEvent; +import org.springframework.statemachine.guard.Guard; /** * Tests for state machine configuration. @@ -34,18 +42,155 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { // tag::snippetA[] @Configuration @EnableStateMachine - public static class Config1 extends EnumStateMachineConfigurerAdapter { + public static class Config1 extends EnumStateMachineConfigurerAdapter { @Override - public void configure(StateMachineStateConfigurer states) throws Exception { + public void configure(StateMachineStateConfigurer states) throws Exception { states .withStates() - .initial(MyStates.S1) - .end(MyStates.SF) - .states(EnumSet.allOf(MyStates.class)); + .initial(States.S1) + .end(States.SF) + .states(EnumSet.allOf(States.class)); } } // end::snippetA[] +// tag::snippetB[] + @Configuration + @EnableStateMachine + public static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(States.S1) + .end(States.SF) + .states(EnumSet.allOf(States.class)) + .and() + .withStates() + .initial(States.S2) + .state(States.S2); + } + + } +// end::snippetB[] + +// tag::snippetC[] + @Configuration + @EnableStateMachine + public static class Config3 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(States.S1) + .end(States.SF) + .states(EnumSet.allOf(States.class)) + .and() + .withStates() + .initial(States.S2) + .state(States.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .and() + .withInternal() + .and() + .withLocal(); + } + + } +// end::snippetC[] + +// tag::snippetD[] + @Configuration + @EnableStateMachine + public static class Config4 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(States.S1) + .target(States.S2) + .event(Events.E1) + .guard(guard()); + } + + @Bean + public Guard guard() { + return new Guard() { + + @Override + public boolean evaluate(StateContext context) { + return true; + } + }; + } + + } +// end::snippetD[] + +// tag::snippetE[] + @Configuration + @EnableStateMachine + public static class Config5 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(States.S1) + .target(States.S2) + .event(Events.E1) + .action(action()); + } + + @Bean + public Action action() { + return new Action() { + + @Override + public void execute(StateContext context) { + // do something + } + }; + } + + } +// end::snippetE[] + +// tag::snippetF[] + @Configuration + @EnableStateMachineFactory + public static class Config6 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(States.S1) + .end(States.SF) + .states(EnumSet.allOf(States.class)); + } + + } +// end::snippetF[] + + +// tag::snippetG[] + static class StateMachineEventListener implements ApplicationListener { + + @Override + public void onApplicationEvent(StateMachineEvent event) { + } + } +// end::snippetG[] + } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/MyEvents.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/Events.java similarity index 82% rename from spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/MyEvents.java rename to spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/Events.java index 332e5a3f..bb86c5db 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/MyEvents.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/Events.java @@ -1,7 +1,7 @@ package org.springframework.statemachine.docs; //tag::snippetA[] -public enum MyEvents { +public enum Events { E1,E2,E3,E4,EF } //end::snippetA[] diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/MyStates.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/States.java similarity index 82% rename from spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/MyStates.java rename to spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/States.java index f2b6bae1..7e7e7198 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/MyStates.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/States.java @@ -1,7 +1,7 @@ package org.springframework.statemachine.docs; //tag::snippetA[] -public enum MyStates { +public enum States { SI,S1,S2,S3,S4,SF } //end::snippetA[] diff --git a/spring-statemachine-samples/build.gradle b/spring-statemachine-samples/build.gradle new file mode 100644 index 00000000..f7902797 --- /dev/null +++ b/spring-statemachine-samples/build.gradle @@ -0,0 +1,6 @@ +description = 'Spring State Machine Samples Common' + +project('spring-statemachine-samples-turnstile') { + description = 'Spring State Machine Turnstile Sample' +} + diff --git a/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java b/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java new file mode 100644 index 00000000..cd4fb59d --- /dev/null +++ b/spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java @@ -0,0 +1,47 @@ +package demo; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.shell.core.CommandMarker; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.statemachine.StateMachine; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +public class AbstractStateMachineCommands implements CommandMarker { + + @Autowired + private StateMachine stateMachine; + + protected StateMachine getStateMachine() { + return stateMachine; + } + + @Autowired + @Qualifier("stateChartModel") + private String stateChartModel; + + @CliCommand(value = "sm state", help = "Prints state machine state") + public String state() { + return StringUtils.collectionToCommaDelimitedString(stateMachine.getState().getIds()); + } + + @CliCommand(value = "sm start", help = "Start a state machine") + public String start() { + stateMachine.start(); + return "State machine started"; + } + + @CliCommand(value = "sm stop", help = "Stop a state machine") + public String stop() { + stateMachine.stop(); + return "State machine stopped"; + } + + @CliCommand(value = "sm print", help = "Print state machine") + public String print() { + return stateChartModel; + } + +} \ No newline at end of file diff --git a/spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java b/spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java new file mode 100644 index 00000000..74020486 --- /dev/null +++ b/spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java @@ -0,0 +1,67 @@ +package demo; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Scanner; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.event.OnStateChangedEvent; +import org.springframework.statemachine.event.OnTransitionEvent; +import org.springframework.statemachine.event.StateMachineEvent; +import org.springframework.statemachine.event.StateMachineEventPublisherConfiguration; + +@Configuration +public class CommonConfiguration { + + private final static Log log = LogFactory.getLog(CommonConfiguration.class); + + @Configuration + @Import(StateMachineEventPublisherConfiguration.class) + static class ApplicationConfig { + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + @Bean + public TestEventListener testEventListener() { + return new TestEventListener(); + } + + @Bean + public String stateChartModel() throws IOException { + ClassPathResource model = new ClassPathResource("statechartmodel.txt"); + InputStream inputStream = model.getInputStream(); + Scanner scanner = new Scanner(inputStream); + String content = scanner.useDelimiter("\\Z").next(); + scanner.close(); + return content; + } + + } + + static class TestEventListener implements ApplicationListener { + + @Override + public void onApplicationEvent(StateMachineEvent event) { + if (event instanceof OnStateChangedEvent) { + OnStateChangedEvent e = (OnStateChangedEvent)event; + log.info("State changed to " + e.getTargetState().getId()); + } else if (event instanceof OnTransitionEvent) { + OnTransitionEvent e = (OnTransitionEvent)event; + log.info("Transition " + e.getTransition().toString()); + } + } + + } + +} diff --git a/spring-statemachine-samples/src/main/java/demo/StateMachinePromptProvider.java b/spring-statemachine-samples/src/main/java/demo/StateMachinePromptProvider.java new file mode 100644 index 00000000..e41be7df --- /dev/null +++ b/spring-statemachine-samples/src/main/java/demo/StateMachinePromptProvider.java @@ -0,0 +1,23 @@ +package demo; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.shell.plugin.support.DefaultPromptProvider; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class StateMachinePromptProvider extends DefaultPromptProvider { + + @Override + public String getPrompt() { + return "sm>"; + } + + + @Override + public String getProviderName() { + return "State machine prompt provider"; + } + +} diff --git a/spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml new file mode 100644 index 00000000..1fc09f1f --- /dev/null +++ b/spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/spring-statemachine-samples/src/main/resources/logback.xml b/spring-statemachine-samples/src/main/resources/logback.xml new file mode 100644 index 00000000..de3dbdc4 --- /dev/null +++ b/spring-statemachine-samples/src/main/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + + %m%n + utf8 + + + + + + + + + + diff --git a/spring-statemachine-samples/turnstile/.gitignore b/spring-statemachine-samples/turnstile/.gitignore new file mode 100644 index 00000000..70e6e4b8 --- /dev/null +++ b/spring-statemachine-samples/turnstile/.gitignore @@ -0,0 +1,19 @@ +.gradle +bin +build +.settings +.classpath +.springBeans +.project +*.iml +*.ipr +*.iws +metastore_db +/samples/pig-scripting/src/main/resources/ml-100k.zip +/samples/pig-scripting/src/main/resources/ml-100k/u.data +/src/test/resources/s3.properties +/.idea/ +.DS_Store +/out/ +target +*.log diff --git a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java new file mode 100644 index 00000000..da63a050 --- /dev/null +++ b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java @@ -0,0 +1,64 @@ +package demo.turnstile; + +import java.util.EnumSet; + +import org.springframework.context.annotation.Configuration; +import org.springframework.shell.Bootstrap; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +@Configuration +public class Application { + +//tag::snippetA[] + @Configuration + @EnableStateMachine + static class StateMachineConfig + extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) + throws Exception { + states + .withStates() + .initial(States.LOCKED) + .states(EnumSet.allOf(States.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) + throws Exception { + transitions + .withExternal() + .source(States.LOCKED) + .target(States.UNLOCKED) + .event(Events.COIN) + .and() + .withExternal() + .source(States.UNLOCKED) + .target(States.LOCKED) + .event(Events.PUSH); + } + + } +//end::snippetA[] + +//tag::snippetB[] + public static enum States { + LOCKED, UNLOCKED + } +//end::snippetB[] + +//tag::snippetC[] + public static enum Events { + COIN, PUSH + } +//end::snippetC[] + + public static void main(String[] args) throws Exception { + Bootstrap.main(args); + } + +} diff --git a/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java new file mode 100644 index 00000000..89377ee6 --- /dev/null +++ b/spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java @@ -0,0 +1,20 @@ +package demo.turnstile; + +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; +import org.springframework.stereotype.Component; + +import demo.AbstractStateMachineCommands; +import demo.turnstile.Application.Events; +import demo.turnstile.Application.States; + +@Component +public class StateMachineCommands extends AbstractStateMachineCommands { + + @CliCommand(value = "sm event", help = "Sends an event to a state machine") + public String event(@CliOption(key = { "", "event" }, mandatory = true, help = "The event") final Events event) { + getStateMachine().sendEvent(event); + return "Event " + event + " send"; + } + +} \ No newline at end of file diff --git a/spring-statemachine-samples/turnstile/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-statemachine-samples/turnstile/src/main/resources/META-INF/spring/spring-shell-plugin.xml new file mode 100644 index 00000000..1fc09f1f --- /dev/null +++ b/spring-statemachine-samples/turnstile/src/main/resources/META-INF/spring/spring-shell-plugin.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/spring-statemachine-samples/turnstile/src/main/resources/statechartmodel.txt b/spring-statemachine-samples/turnstile/src/main/resources/statechartmodel.txt new file mode 100644 index 00000000..a551ef9a --- /dev/null +++ b/spring-statemachine-samples/turnstile/src/main/resources/statechartmodel.txt @@ -0,0 +1,19 @@ ++----------------------------------------------------------------+ +| SM | ++----------------------------------------------------------------+ +| | +| +----------------+ +----------------+ | +| *-->| LOCKED | | UNLOCKED | | +| +----------------+ +----------------+ | +| +---| entry/ | | entry/ |---+ | +| | | exit/ | | exit/ | | | +| | | | | | | | +| PUSH| | |---COIN-->| | |COIN | +| | | | | | | | +| | | | | | | | +| | | |<--PUSH---| | | | +| +-->| | | |<--+ | +| | | | | | +| +----------------+ +----------------+ | +| | ++----------------------------------------------------------------+