diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc index e0c2cdb3..e15bfb95 100644 --- a/docs/src/reference/asciidoc/appendix.adoc +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -29,7 +29,7 @@ Assuming we have states _STATE1_, _STATE2_ and events _EVENT1_, _EVENT2_, logic of state machine can be defined as shown in below quick example. -image::images/statechart0.png[] +image::images/statechart0.png[width=500] [source,java,indent=0] ---- @@ -129,16 +129,53 @@ A transition is a relationship between a source state and a target state. A switch from a state to another is a _state transition_ caused by a _trigger_. +===== Internal Transition +Internal transition is used when action needs to be executed without +causing a state transition. With internal transition source and target +state is always a same and it is identical with self-transition in the +absence of state entry and exit actions. + +===== External vs. Local Transition +Most of the cases external and local transition are functionally +equivalent expect in cases where transition is happening between super +and sub states. Local transition doesn't cause exit and entry to +source state if target state is a substate of a source state. Other +way around, local transition doesn't cause exit and entry to target +state if target is a superstate of a source state. + +image::images/statechart4.png[width=500] + +Above image shows a different between local and external transitions +with a very simplistic super and sub states. + ==== Actions Actions are the ones which really glues state machine state changes with a users own code. State machine can execute action on various changes and steps in a state machine like entering or exiting a state, or doing a state transition. +Actions usually have access to a state context which gives running +code a choice to interact with a state machine in a various ways. +State context i.e. is exposing a whole state machine so user can +access extended state variables, event headers if transition is based +on an event, or actual transition where it is possible to see more +detailed where this state change is coming from and where it is going. + ==== Hierarchical State Machines Concept of a hierarchical state machine is used to simplify state design when particular states can only exist together. +Hierarchical states are really an innovation in UML state machine over +a traditional state machines like Mealy or Moore machines. +Hierarchical states allows to define some level of abstraction is a +sense how java developer would define a class structure with abstract +classes. For example having a nested state machine user is able to +define transition on a multiple level of states possibly with a +different conditions. State machine will always try to see if current +state is able to handle an event together with a transition guard +conditions. If these conditions are not evaluated to true, state +machine will simply see what a super state can handle. + ==== Regions Regions which are also called as orthogonal regions are usually viewed as exclusive OR operation applied to a states. Concept of a region in diff --git a/docs/src/reference/asciidoc/faq.adoc b/docs/src/reference/asciidoc/faq.adoc new file mode 100644 index 00000000..5f00a7ef --- /dev/null +++ b/docs/src/reference/asciidoc/faq.adoc @@ -0,0 +1,43 @@ +[[statemachine-faq]] += FAQ +This chapter tries to give solutions to question user is most likely +to ask. + +== State Changes + +.I want to transit to next state automatically + +{zwsp} + + +There are few choices a state machine developer can choose. + +* Implement an action and send appropriate event into a state machine + which triggers a transition into a proper target state. +* Define deferred event within a state and before sending an event + send a event which will be deferred and thus causing next + appropriate state transition when it is more convenient to handle + that event. +* Implement a triggerless transition which will automatically cause + state transition into a next state when state has entry and its + actions has been completed. + +.How do I defer an event + +{zwsp} + + +For more complete example and explanation, see cdplayer sample. + +== Extented State + +.How I can initialise variables on state machine start + +{zwsp} + + +Important concept in a state machine is that nothing really happens +unless there is a trigger which is causing a state transition which +then can fire actions. However, having said that, Spring Statemachine +always have an initial transition when state machine is started. With +this initial transition user can execute a simple action which within +a _StateContext_ can do whatever it likes with an extended state +variables. + diff --git a/docs/src/reference/asciidoc/images/statechart2.png b/docs/src/reference/asciidoc/images/statechart2.png index 01548282..a855d1c7 100644 Binary files a/docs/src/reference/asciidoc/images/statechart2.png and b/docs/src/reference/asciidoc/images/statechart2.png differ diff --git a/docs/src/reference/asciidoc/images/statechart4.png b/docs/src/reference/asciidoc/images/statechart4.png new file mode 100644 index 00000000..c6064553 Binary files /dev/null and b/docs/src/reference/asciidoc/images/statechart4.png differ diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc index 60400118..d69eb415 100644 --- a/docs/src/reference/asciidoc/index.adoc +++ b/docs/src/reference/asciidoc/index.adoc @@ -36,4 +36,5 @@ include::introduction.adoc[] include::sm.adoc[] include::sm-examples.adoc[] +include::faq.adoc[] include::appendix.adoc[] diff --git a/docs/src/reference/asciidoc/introduction.adoc b/docs/src/reference/asciidoc/introduction.adoc index d6433a83..333b469a 100644 --- a/docs/src/reference/asciidoc/introduction.adoc +++ b/docs/src/reference/asciidoc/introduction.adoc @@ -10,18 +10,25 @@ familiar with state machine concepts. == Requirements Spring Statemachine {revnumber} is built and tested with JDK 7 and Spring -Framework {spring-version}. +Framework {spring-version} and doesn't require any other dependencies +outside of Spring Framework. Samples will require spring-shell and +spring-boot which will pull other dependencies beyond Framework +itself. == Background -State machines are powerful because behaviour is always supposed to be +State machines are powerful because behaviour is always guaranteed to be consistent and relatively easily debugged due to ways how operational rules are written in stone when machine is started. Idea is that your application is and may exist in a finite number of states and then something -happens which takes your application from one state to the next. +happens which takes your application from one state to the next. What +will drive a state machine are triggers which are either based on an +events or timers. It is much easier to design high level logic outside of your -application and then interact witha state machine with a various -different ways. +application and then interact with a state machine with a various +different ways. You will simple send interact with a state machine by +sending event, listening what a state machine does or simply request a +current state. Traditionally state machines are added to a existing project when developer realizes that code base is starting to look like a plate @@ -31,7 +38,7 @@ ask developer to go home when things are starting to look too complex. == Usage Scenarios -Project is a good candiate to use state machines if: +Project is a good candiate to use state machine if: * Application or part of its structure can be represented as states. * You want to split complex logic into smaller manageable tasks. @@ -47,3 +54,12 @@ You are already trying to implement a state machine if: enum is set and then making further exceptions what to do when certain combination of your flags and enums exists or doesn't exist together. +== Feature Limitations + +A lot of features are planned thus not all expected feature are ntot +yet implemented: + +* Regions are not fully supported and currently contains many bugs. +* Features around pseudo states are missing functionality like history + states, fork/join with regions, etc. + diff --git a/docs/src/reference/asciidoc/preface.adoc b/docs/src/reference/asciidoc/preface.adoc index ac0cb2ad..c600acaf 100644 --- a/docs/src/reference/asciidoc/preface.adoc +++ b/docs/src/reference/asciidoc/preface.adoc @@ -18,5 +18,7 @@ This reference documentations contains following parts. <> more detailed state machine samples +<> frequently ask questions + <> generic info about used material and state machines diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 84142d79..c5d44e72 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -15,7 +15,7 @@ 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[] +image::images/statechart1.png[width=500] .States [source,java,indent=0] @@ -79,7 +79,7 @@ Event PUSH send Showcase is a complex state machine showing all possible transition topologies up to four levels of state nesting. -image::images/statechart2.png[width=200] +image::images/statechart2.png[width=500] .States [source,java,indent=0] @@ -93,19 +93,31 @@ include::samples/demo/showcase/Application.java[tags=snippetB] include::samples/demo/showcase/Application.java[tags=snippetC] ---- -.Configuration +.Configuration - states [source,java,indent=0] ---- -include::samples/demo/showcase/Application.java[tags=snippetA] +include::samples/demo/showcase/Application.java[tags=snippetAA] ---- -.Guard +.Configuration - transitions +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetAB] +---- + +.Configuration - actions and guard +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetAC] +---- + +.Action [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetD] ---- -.Action +.Guard [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetE] @@ -133,14 +145,25 @@ and probably few nested if/else clauses, that will do the job, but what about if you need to make all this behaviour much more complex, do you really want to keep adding more flags and if/else clauses. -image::images/statechart3.png[] +image::images/statechart3.png[width=500] Lets go throught how this sample and its state machine is designed and -how those two interacts with each other. +how those two interacts with each other. Below three config sections +are used withing a _EnumStateMachineConfigurerAdapter_. [source,java,indent=0] ---- -include::samples/demo/cdplayer/Application.java[tags=snippetA] +include::samples/demo/cdplayer/Application.java[tags=snippetAA] +---- + +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetAB] +---- + +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetAC] ---- What we did in above configuration: @@ -160,9 +183,14 @@ needed to automatically track elapsed time within a playing track and to have facility to make a decision when to switch to next track. ** With event _PLAY_ if source state is _IDLE_ and target state is _BUSY_ we defined action _playAction_ and guard _playGuard_. -** Lastly with event _LOAD_ and state _OPEN_ we defined internal +** With event _LOAD_ and state _OPEN_ we defined internal transition with action _loadAction_ which will insert cd disc into extended state variables. +** _PLAYING_ state defined three internal transitions where one is +triggered by a timer executing a _playingAction_ which updates +extended state variables. Other two transitions are with _trackAction_ +with different events, _BACK_ and _FORWARD_ respectively which handles +when user wants to go back or forward in tracks. This machine only have six states which are introduced as an enum. [source,java,indent=0] @@ -227,5 +255,19 @@ disc has been loaded. include::samples/demo/cdplayer/Application.java[tags=snippetJ] ---- -Now lets see how this cd player works and we can go a little deeper in -its state machine logic. +_PlayingAction_ is updating extended state variable _ELAPSEDTIME_ which +cd player itself can read and update lcd status. Action also handles +track shift if user is going back or forward in tracks. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetK] +---- + +_TrackAction_ handles track shift action if user is going back or forward +in tracks. If it is a last track of a cd, playing is stopped and _STOP_ +event sent to a state machine. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetL] +---- + diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 4ef176af..ec5ee568 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -14,7 +14,6 @@ that Spring Statemachine provides to any Spring based application. [[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 Statemachine is configured and how it leverages Spring's lightweight @@ -22,10 +21,10 @@ IoC containers to simplify the application internals to make it more manageable. === 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 +machine you just use `EnumStateMachineConfigurerAdapter` and define +possible states, choose initial and optional end state. [source,java,indent=0] ---- @@ -33,6 +32,9 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetA] ---- === Configuring Hierarchical States +Hierarchical states can be defined by using multiple `withStates()` +calls where `parent()` can be used to indicate that these +particular states are sub-states of some other state. [source,java,indent=0] ---- @@ -40,6 +42,9 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetB] ---- === Configuring Transitions +We support three different types of transitions, `external`, +`internal` and `local`. Transitions are either triggered by a signal +which is an event sent into a state machine or a timer. [source,java,indent=0] ---- @@ -47,13 +52,26 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetC] ---- === Configuring Guards +Guards are used to protect state transitions. Interface _Guard_ is +used to do an evaluation where method has access to _StateContext_. [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetD] ---- +In above two different types of guard configuration is used. Firstly a +simply _Guard_ is created as a bean and attached to transition between +states `S1` and `S2`. + +Secondly a simple spel expression can be used as a guard where +expression must return a `Boolean` value. Behind a scenes this spel +based guard is a _SpelExpressionGuard_. This was attached to +transition between states `S2` and `S3`. Both guard in above sample +always evaluate to true. + === Configuring Actions +Actions can be defined with various steps within a state transitions. [source,java,indent=0] ---- @@ -127,19 +145,44 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetG] ---- === State Machine Listener -For using _StateMachineListener_ you can either extend it and +Using _StateMachineListener_ you can either extend it and implement all callback methods or use _StateMachineListenerAdapter_ class which contains stub method implementations and choose which ones to override. -=== Limitations and Problems -TBD ctx events may create too much traffic, etc. - [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetH] ---- +In above example we simply created our own listener class +_StateMachineEventListener_ which extends +_StateMachineListenerAdapter_. + +Once you have your own listener defined, it can be registered into a +state machine via its interface as shown below. It's just a matter of +flavour if it's hooked up within a spring configuration or done +manually at any time of application life-cycle. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetM] +---- + +=== Limitations and Problems +Spring application context is not a fastest event bus out there so it +is advised to give some thought what is a rate of events state machine +is sending. For better performance it may be better to use +_StateMachineListener_ interface. For this specific reason it is +possible to use `contextEvents` flag with _@EnableStateMachine_ and +_@EnableStateMachineFactory_ to disable Spring application context +events as shown above. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetN] +---- + [[sm-context]] == Context Integration It is a little limited to do interaction with a state machine by diff --git a/docs/src/statecharts/statechart4.txt b/docs/src/statecharts/statechart4.txt new file mode 100644 index 00000000..408bb72f --- /dev/null +++ b/docs/src/statecharts/statechart4.txt @@ -0,0 +1,21 @@ ++---------------------------------------------------------+ +| | +| LOCAL EXTERNAL | +| +-------------------+ +-------------------+ | +| | +----------+ | | +----------+ | | +| | | | | +-------->| | | | +| |----->| | | | | | | | | +| | | | | +--| | | | | +| | +----------+ | | +----------+ | | +| +-------------------+ +-------------------+ | +| | +| | +| +-------------------+ +-------------------+ | +| | +----------+ | | +----------+ | | +| | | | | +---------| | | | +| |<-----| | | | | | | | | +| | | | | +->| | | | | +| | +----------+ | | +----------+ | | +| +-------------------+ +-------------------+ | +| | ++---------------------------------------------------------+ 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 3d08a084..2ce64e51 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 @@ -79,10 +79,10 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { states .withStates() .initial(States.S1) - .end(States.SF) - .states(EnumSet.allOf(States.class)) + .state(States.S1) .and() .withStates() + .parent(States.S1) .initial(States.S2) .state(States.S2); } @@ -100,22 +100,23 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { states .withStates() .initial(States.S1) - .end(States.SF) - .states(EnumSet.allOf(States.class)) - .and() - .withStates() - .initial(States.S2) - .state(States.S2); + .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer transitions) throws Exception { transitions .withExternal() + .source(States.S1).target(States.S2) + .event(Events.E1) .and() .withInternal() + .source(States.S2) + .event(Events.E2) .and() - .withLocal(); + .withLocal() + .source(States.S2).target(States.S3) + .event(Events.E3); } } @@ -130,10 +131,15 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { public void configure(StateMachineTransitionConfigurer transitions) throws Exception { transitions .withExternal() - .source(States.S1) - .target(States.S2) + .source(States.S1).target(States.S2) .event(Events.E1) - .guard(guard()); + .guard(guard()) + .and() + .withExternal() + .source(States.S2).target(States.S3) + .event(Events.E2) + .guardExpression("true"); + } @Bean @@ -280,4 +286,32 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { } // end::snippetL[] +// tag::snippetM[] + static class Config7 { + + @Autowired + StateMachine stateMachine; + + @Bean + public StateMachineEventListener stateMachineEventListener() { + StateMachineEventListener listener = new StateMachineEventListener(); + stateMachine.addStateListener(listener); + return listener; + } + + } +// end::snippetM[] + +// tag::snippetN[] + @Configuration + @EnableStateMachine(contextEvents = false) + public static class Config8 extends EnumStateMachineConfigurerAdapter { + } + + @Configuration + @EnableStateMachineFactory(contextEvents = false) + public static class Config9 extends EnumStateMachineConfigurerAdapter { + } +// end::snippetN[] + } diff --git a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java index b8c46df2..15e8796b 100644 --- a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java +++ b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java @@ -23,12 +23,12 @@ import org.springframework.statemachine.guard.Guard; @Configuration public class Application { -//tag::snippetA[] @Configuration @EnableStateMachine static class StateMachineConfig extends EnumStateMachineConfigurerAdapter { +//tag::snippetAA[] @Override public void configure(StateMachineStateConfigurer states) throws Exception { @@ -53,7 +53,9 @@ public class Application { .state(States.PAUSED); } +//end::snippetAA[] +//tag::snippetAB[] @Override public void configure(StateMachineTransitionConfigurer transitions) throws Exception { @@ -97,7 +99,9 @@ public class Application { .withInternal() .source(States.OPEN).event(Events.LOAD).action(loadAction()); } +//end::snippetAB[] +//tag::snippetAC[] @Bean public ClosedEntryAction closedEntryAction() { return new ClosedEntryAction(); @@ -127,9 +131,9 @@ public class Application { public PlayGuard playGuard() { return new PlayGuard(); } +//end::snippetAC[] } -//end::snippetA[] //tag::snippetB[] diff --git a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java index 12294276..3757eeb2 100644 --- a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java +++ b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java @@ -14,12 +14,12 @@ import org.springframework.statemachine.guard.Guard; @Configuration public class Application { -//tag::snippetA[] @Configuration @EnableStateMachine static class StateMachineConfig extends EnumStateMachineConfigurerAdapter { +//tag::snippetAA[] @Override public void configure(StateMachineStateConfigurer states) throws Exception { @@ -53,7 +53,9 @@ public class Application { .initial(States.S211) .state(States.S211); } +//end::snippetAA[] +//tag::snippetAB[] @Override public void configure(StateMachineTransitionConfigurer transitions) throws Exception { @@ -112,7 +114,9 @@ public class Application { .source(States.S11).target(States.S12).event(Events.I); } +//end::snippetAB[] +//tag::snippetAC[] @Bean public FooGuard foo0Guard() { return new FooGuard(0); @@ -127,9 +131,9 @@ public class Application { public FooAction fooAction() { return new FooAction(); } +//end::snippetAC[] } -//end::snippetA[] //tag::snippetB[] public static enum States {