From ed81b1b578e9a1cabb98d7591d264036a04515ab Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Fri, 6 Mar 2015 15:01:23 +0000 Subject: [PATCH] Updates to docs --- .gitignore | 1 + build.gradle | 26 ++- docs/src/reference/asciidoc/appendix.adoc | 25 +-- .../reference/asciidoc/images/statechart1.png | Bin 0 -> 6430 bytes docs/src/reference/asciidoc/introduction.adoc | 3 + docs/src/reference/asciidoc/preface.adoc | 3 - docs/src/reference/asciidoc/sm-examples.adoc | 75 ++++++++- docs/src/reference/asciidoc/sm.adoc | 81 ++++++++- docs/src/statecharts/statechart1.txt | 19 +++ settings.gradle | 13 ++ .../docs/DocsConfigurationSampleTests.java | 155 +++++++++++++++++- .../docs/{MyEvents.java => Events.java} | 2 +- .../docs/{MyStates.java => States.java} | 2 +- spring-statemachine-samples/build.gradle | 6 + .../demo/AbstractStateMachineCommands.java | 47 ++++++ .../main/java/demo/CommonConfiguration.java | 67 ++++++++ .../java/demo/StateMachinePromptProvider.java | 23 +++ .../META-INF/spring/spring-shell-plugin.xml | 8 + .../src/main/resources/logback.xml | 17 ++ .../turnstile/.gitignore | 19 +++ .../main/java/demo/turnstile/Application.java | 64 ++++++++ .../demo/turnstile/StateMachineCommands.java | 20 +++ .../META-INF/spring/spring-shell-plugin.xml | 8 + .../src/main/resources/statechartmodel.txt | 19 +++ 24 files changed, 673 insertions(+), 30 deletions(-) create mode 100644 docs/src/reference/asciidoc/images/statechart1.png create mode 100644 docs/src/statecharts/statechart1.txt rename spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/{MyEvents.java => Events.java} (82%) rename spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/{MyStates.java => States.java} (82%) create mode 100644 spring-statemachine-samples/build.gradle create mode 100644 spring-statemachine-samples/src/main/java/demo/AbstractStateMachineCommands.java create mode 100644 spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java create mode 100644 spring-statemachine-samples/src/main/java/demo/StateMachinePromptProvider.java create mode 100644 spring-statemachine-samples/src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 spring-statemachine-samples/src/main/resources/logback.xml create mode 100644 spring-statemachine-samples/turnstile/.gitignore create mode 100644 spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/Application.java create mode 100644 spring-statemachine-samples/turnstile/src/main/java/demo/turnstile/StateMachineCommands.java create mode 100644 spring-statemachine-samples/turnstile/src/main/resources/META-INF/spring/spring-shell-plugin.xml create mode 100644 spring-statemachine-samples/turnstile/src/main/resources/statechartmodel.txt 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 0000000000000000000000000000000000000000..2d6e0478b4df89d0c149da2756608836f45d126b GIT binary patch literal 6430 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 literal 0 HcmV?d00001 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---| | | | +| +-->| | | |<--+ | +| | | | | | +| +----------------+ +----------------+ | +| | ++----------------------------------------------------------------+