From 16a2a5389d7a8be829ae11bbf859536978179f48 Mon Sep 17 00:00:00 2001 From: Jay Bryant Date: Wed, 3 Apr 2019 09:14:31 -0500 Subject: [PATCH] Editing pass I edited for spelling, punctuation, grammar, usage, and corporate voice. I also added leader sentences before all the images and code listings and rewrote for clarity. --- docs/src/reference/asciidoc/appendix.adoc | 802 ++--- docs/src/reference/asciidoc/faq.adoc | 37 +- .../reference/asciidoc/getting-started.adoc | 163 +- docs/src/reference/asciidoc/index.adoc | 8 + docs/src/reference/asciidoc/introduction.adoc | 64 +- docs/src/reference/asciidoc/preface.adoc | 25 +- docs/src/reference/asciidoc/recipes.adoc | 151 +- docs/src/reference/asciidoc/sm-examples.adoc | 1410 +++++--- docs/src/reference/asciidoc/sm.adoc | 2938 +++++++++-------- docs/src/reference/asciidoc/whatsnew.adoc | 77 +- 10 files changed, 3200 insertions(+), 2475 deletions(-) diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc index 253ba22f..5bd26317 100644 --- a/docs/src/reference/asciidoc/appendix.adoc +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -5,11 +5,15 @@ [appendix] == Support Content -This appendix provides generic information about used classes and -material in this reference documentation. + +This appendix provides generic information about the classes and +material that are used in this reference documentation. === Classes Used in This Document +The following listings show the classes used throughout this reference guide: + +==== [source,java,indent=0] ---- include::samples/States.java[tags=snippetA] @@ -29,18 +33,23 @@ include::samples/States3.java[tags=snippetA] ---- include::samples/Events.java[tags=snippetA] ---- +==== [appendix] == State Machine Concepts -This appendix provides generic information about state machines. + +This appendix provides generial information about state machines. === Quick Example -Assuming we have states _STATE1_, _STATE2_ and events _EVENT1_, -_EVENT2_, logic of state machine can be defined as shown in below -quick example. + +Assuming we have states named `STATE1` and `STATE2` and events named `EVENT1` and +`EVENT2`, you can define the logic of the state machine as the following image shows: image::images/statechart0.png[width=500] +The following listings define the state machine in the preceding image: + +==== [source,java,indent=0] ---- include::samples/IntroSample.java[tags=snippetA] @@ -60,22 +69,23 @@ include::samples/IntroSample.java[tags=snippetC] ---- include::samples/IntroSample.java[tags=snippetD] ---- +==== [glossary] === Glossary *State Machine*:: -Main entity driving a collection of states together with regions, -transitions and events. +The main entity that drives a collection of states, together with regions, +transitions, and events. *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. +holds. The state is the main entity of a state machine where state changes +are driven by events. *Extended State*:: An extended state is a special set of variables kept in a state -machine to reduce number of needed states. +machine to reduce the number of needed states. *Transition*:: A transition is a relationship between a source state and a target @@ -85,440 +95,479 @@ response of the state machine to an occurrence of an event of a particular type. *Event*:: -An entity which is send to a state machine which then drives a various +An entity that is sent to a state machine and then drives a various state changes. *Initial State*:: -A special state in which the state machine starts. Initial state is +A special state in which the state machine starts. The initial state is always bound to a particular state machine or a region. A state -machine with a multiple regions may have a multiple initial states. +machine with multiple regions may have a multiple initial states. *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 +(Also called as a final state.) 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 +state machine are also completed, the entire state machine is completed. *History State*:: -A pseudo state which allows a state machine to remember its last -active state. Two types of history state exists, _shallow_ which only -remember top level state and _deep_ which remembers active states in a -sub-machines. +A pseudo state that lets a state machine remember its last +active state. Two types of history state exists: _shallow_ (which +remembers only top level state) and _deep_ (which remembers active states in +sub-machines). *Choice State*:: -A pseudo state which allows to make a transition choice based of i.e. +A pseudo state that allows for making a transition choice based on (for example) event headers or extended state variables. *Junction State*:: -A pseudo state which is relatively similar to choice state but allows -multiple incoming transitions while choice only allows one incoming +A pseudo state that is relatively similar to choice state but allows +multiple incoming transitions, while choice allows only one incoming transition. *Fork State*:: -A pseudo state which gives a controlled entry into a regions. +A pseudo state that gives controlled entry into a region. *Join State*:: -A pseudo state which gives a controlled exit from a regions. +A pseudo state that gives controlled exit from a region. *Entry Point*:: -A pseudo state which allows a controlled entry into a submachine. +A pseudo state that allows controlled entry into a submachine. *Exit Point*:: -A pseudo state which allows a controlled exit from a submachine. +A pseudo state that allows controlled exit from a submachine. *Region*:: A region is an orthogonal part of either a composite state or a state machine. It contains states and transitions. *Guard*:: -Is a boolean expression evaluated dynamically based on the value of +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. +only when they evaluate to `TRUE` and disabling them when they evaluate +to `FALSE`. *Action*:: -A action is a behaviour executed during the triggering of the +A action is a behavior run during the triggering of the transition. [[crashcourse]] -=== A State Machines Crash Course -This appendix provides generic crash course to a state machine +=== A State Machine Crash Course + +This appendix provides a generic crash course to state machine concepts. ==== States -A state is a model which a state machine can be in. It is always -easier to describe state as a real world example rather than trying to -abstract concepts with a generic documentation. For example let's take -a simple example of a keyboard most of us are using every single day. -If you have a full keyboard which has normal keys on a left side and -the numeric keypad on a right side you may have noticed that the -numeric keypad may be in a two different states depending whether -numlock is activated or not. If it is not active then typing will -result navigation using arrows, etc. If numpad is active then typing -will result numbers to be used. Essentially numpad part of a keyboard + +A state is a model in which a state machine can be. It is always +easier to describe state as a real world example rather than trying to use +abstract concepts ingeneric documentation. To that end, consider +a simple example of a keyboard -- most of us use one every single day. +If you have a full keyboard that has normal keys on the left side and +the numeric keypad on the right side, you may have noticed that the +numeric keypad may be in a two different states, depending on whether +numlock is activated. If it is not active, pressing the number pad keys +result in navigation by using arrows and so on. If the number pad is active, pressing +those keys results in numbers being typed. Essentially, the number pad part of a keyboard can be in two different states. -To relate state concept to programming it means that instead of using -flags, nested if/else/break clauses or other impractical logic you -simply rely on state, state variables or other interaction with a +To relate state concept to programming, it means that instead of using +flags, nested if/else/break clauses, or other impractical (and sometimes tortuous) logic, you can +rely on state, state variables, or another interaction with a state machine. ==== Pseudo States -PseudoState is a special type of state which usually introduces more -higher level logic into a state machine by either giving a state a -special meaning like initial state. State machine can then internally -react to these states by doing various actions available in UML state + +Pseudostate is a special type of state that usually introduces more +higher-level logic into a state machine by either giving a state a +special meaning (such as initial state). A state machine can then internally +react to these states by doing various actions that are available in UML state machine concepts. ===== Initial -*Initial pseudostate* state is always needed for every single state -machine whether you have a simple one level state machine or more -complex state machine composed with submachines or regions. Initial -state simple defines where state machine should go when it starts and -without it state machine is ill-formed. + +The *Initial pseudostate* state is always needed for every single state +machine, whether you have a simple one-level state machine or a more +complex state machine composed of submachines or regions. The initial +state defines where a state machine should go when it starts. +Without it, a state machine is ill-formed. ===== End -*Terminate pseudostate* which is also called as end state will indicate -that a particular state machine has reached its final state. Effectively -this mean that a state machine will no longer process any events and will -not transit to any other state. However in a case of submachines are -regions, state machine is able to restart from its terminal state. + +The *Terminate pseudostate* (which is also called "`end state`") indicates +that a particular state machine has reached its final state. Effectively, +this mean that a state machine no longer processes any events and does +not transit to any other state. However, in the case where submachines are +regions, a state machine can restart from its terminal state. ===== Choice -*Choice pseudostate* is used to choose a dynamic conditional branch of -a transition from this state. Dynamic condition is evaluated by guards -so that at least one and at most one branch is selected. Usually a -simple if/elseif/else structure is used to make sure that at least one -branch is selected. Otherwise state machine might end up in a deadlock -and configuration would be ill-formed. + +You can use the *Choice pseudostate* choose a dynamic conditional branch of +a transition from this state. The dynamic condition is evaluated by guards +so that one branch is selected. Usually a +simple if/elseif/else structure is used to make sure that one +branch is selected. Otherwise, the state machine might end up in a deadlock, +and the configuration is ill-formed. ===== Junction -*Junction pseudostate* is functionally similar than choice as both are -implemented with if/elseif/else structure. Only real difference is -that junction allows multiple incoming transitions while choice only -allows one. Thus difference is purely academic but have some -differences i.e. when state machine is designed using real UI modeling + +The *Junction pseudostate* is functionally similar to choice, as both are +implemented with if/elseif/else structures. The only real difference is +that junction allows multiple incoming transitions, while choice +allows only one. Thus difference is largely academic but does have some +differences, such as when a state machine is designed is used with a real UI modeling framework. ===== History -*History pseudostate* can be used to remember a last active state -configuration. After state machine has been exited, history state can -be used to restore previous knows configuration. There are two types -of history states available, _SHALLOW_ only remember active state of a -state machine itself while _DEEP_ also remembers nested states. -History state could be implemented externally by listening state -machine events but this would soon make logic very difficult to work -with, especially if state machine contains complex nested structures. -Letting state machine itself to handle recording of history states -makes things much simpler. What is left for user to do is simply do a -transition into a history state and state machine will hand the needed +You can use the *History pseudostate* to remember the last active state +configuration. After a state machine has exited, you can use a history state +to restore a previously known configuration. There are two types +of history states available: `SHALLOW` (which remembers only the active state of a +state machine itself) and `DEEP` (which also remembers nested states). + +A history state could be implemented externally by listening state +machine events, but this would soon make for very difficult logic, +especially if a state machine contains complex nested structures. +Letting the state machine itself handle the recording of history states +makes things much simpler. The user need only create a +transition into a history state, and the state machine handles the needed logic to go back to its last known recorded state. In cases where a Transition terminates on a history state when the state -has not been entered before (i.e., no prior history) or it had reached its -End State, there is an option to force a transition to a specific substate, -using the default history mechanism. This is a Transition that originates -in the history state and terminates on a specific Vertex (the default history -state) of the Region containing the history state. This Transition is only -taken if execution leads to the history state and the state had never been -active before. Otherwise, the normal history entry into the Region is executed. -If no default history transition is defined, then standard default entry of +has not been previously entered (in other words, no prior history exists) or it had reached its +end state, a transition can force the state machine to a specific substate, by +using the default history mechanism. This transition originates +in the history state and terminates on a specific vertex (the default history +state) of the region that contains the history state. This transition is +taken only if its execution leads to the history state and the state had never before been +active. Otherwise, the normal history entry into the region is executed. +If no default history transition is defined, the standard default entry of the region is performed. ===== Fork -*Fork pseudostate* can be used to do an explicit entry into one or more regions. + +You can use the *Fork pseudostate* to do an explicit entry into one or more regions. +The following image shows how a fork works: image::images/statechart7.png[width=500] -Target state can be a parent state hosting regions, which simply -means that regions are activated by entering its initial states. It's -also possible to add targets directly to any state in a region which +The target state can be a parent state that hosts regions, which simply +means that regions are activated by entering their initial states. You +can also add targets directly to any state in a region, which allows more controlled entry into a state. ===== Join -*Join pseudostate* is used to merge several transitions together -originating from different regions. It is generally used to wait + +The *Join pseudostate* merges together several transitions that +originate from different regions. It is generally used to wait and block for participating regions to get into its join target states. +The following image shows how a join works: image::images/statechart8.png[width=500] -Source state can be a parent state hosting regions, which means that -join states will be a terminate states of a participating regions. -It's also possible to define source states to be any state in a -regions which allows controlled exit from a regions. +The source state can be a parent state that hosts regions, which means that +join states are the terminal states of the participating regions. +You can also define source states to be any state in a +region, which allows controlled exit from regions. ===== Entry Point -An _Entry Point_ pseudostate represents an entry point for a state + +An *Entry Point pseudostate* represents an entry point for a state machine or a composite state that provides encapsulation of the insides of the state or state machine. In each region of the state machine or -composite state owning the _Entry Point_ , there is at most a single -transition from the entry point to a Vertex within that Region. +composite state that owns the entry point, there is at most a single +transition from the entry point to a vertex within that region. ===== Exit Point -An _Exit Point_ pseudostate is an exit point of a state machine or + +An *Exit Point pseudostate* is an exit point of a state machine or composite state that provides encapsulation of the insides of the state -or state machine. Transitions terminating on an _Exit Point_ within any -region of the composite state or a state machine referenced by a -submachine state implies exiting of this composite state or submachine +or state machine. Transitions that terminate on an exit point within any +region of the composite state (or a state machine referenced by a +submachine state) imply exiting of this composite state or submachine state (with execution of its associated exit behavior). ==== Guard Conditions -Guard conditions are expressions which evaluates either to *TRUE* or -*FALSE* based on extended state variables and event parameters. Guards -are used with actions and transitions to dynamically choose if -particular action or transition should be executed. Aspects of guards, -event parameters and extended state variables are simply to make state + +Guard conditions are expressions which evaluates to either `TRUE` or +`FALSE`, based on extended state variables and event parameters. Guards +are used with actions and transitions to dynamically choose whether a +particular action or transition should be run. The various spects of guards, +event parameters, and extended state variables exist to make state machine design much more simple. ==== Events -Event is the most used trigger behaviour to drive a state machine. -There are other ways to trigger behaviour to happen in state machine -like a timer but events are the ones which really allows user to -interact with a state machine. Events are also called as signals to -possibly alter a state machine state. + +Event is the most-used trigger behavior to drive a state machine. +There are other ways to trigger behavior in a state machine +(such as a timer), but events are the ones that really let users +interact with a state machine. Events are also called "`signals`". +They basically indicate something that can possibly alter a state machine state. ==== Transitions + 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_. +state. A switch from one 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 + +Internal transition is used when an action needs to be run without +causing a state transition. In an internal transition, the source state and the target +state is always the same, and it is identical with a 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 except 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. +===== External versus Local Transitions + +In most cases, external and local transitions are functionally +equivalent, except in cases where the transition happens between super +and sub states. Local transitions do not cause exit and entry to a +source state if the target state is a substate of a source state. +Conversely, local transitions do not cause exit and entry to a target +state if the target is a superstate of a source state. +The following image shows the difference between local and external transitions +with very simplistic super and sub states: image::images/statechart4.png[width=500] -Above image shows a different between local and external transitions -with a very simplistic super and sub states. +==== Triggers + +A trigger begins a transition. Triggers can be driven by either events or timers. ==== Actions -Actions are the ones which really glues state machine state changes -with a user's own code. State machine can execute action on various -changes and steps in a state machine like entering or exiting a state, + +Actions really glue state machine state changes +to a user's own code. A state machine can run an action on various +changes and on the steps in a state machine (such as 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. +Actions usually have access to a state context, which gives running +code a choice to interact with a state machine in various ways. +State context exposes a whole state machine so that a user can +access extended state variables, event headers (if a transition is based +on an event), or an actual transition (where it is possible to see more +detailed about 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. +The concept of a hierarchical state machine is used to simplify state +design when particular states must exist together. + +Hierarchical states are really an innovation in UML state machines over +traditional state machines, such as Mealy or Moore machines. +Hierarchical states lets you define some level of abstraction (parallel +to how a Java developer might define a class structure with abstract +classes). For example, with a nested state machine, you can +define transition on a multiple level of states (possibly with +different conditions). A state machine always tries to see if the current +state is able to handle an event, together with transition guard +conditions. If these conditions do not evaluate to `TRUE`, the state +machine merely see what the 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 -terms of a state machine is usually a little difficult to understand + +Regions (which are also called as orthogonal regions) are usually viewed +as exclusive OR (XOR) operations applied to states. The concept of a region in +terms of a state machine is usually a little difficult to understand, but things gets a little simpler with a simple example. -Some of us have a full size keyboard with main keys on a left side and numeric -keys on a right side. You've probably noticed that both sides really -have their own state which you see if you press a numlock key which -only alters behaviour of numpad itself. If you don't have a full size -keyboard you can buy a simple external usb numpad having only numpad -part of a keys. If left and right side can freely exist without the -other they must have a totally different states which means they are -operating on different state machines. +Some of us have a full size keyboard with the main keys on the left side and numeric +keys on the right side. You have probably noticed that both sides really +have their own state, which you see if you press a "`numlock`" key (which +alters only the behaviour of the number pad itself). If you do not have a full-size +keyboard, you can buy an external USB number pad. +Given that the left and right side of a keyboard can each exist without the +other, they must have totally different states, which means they are +operating on different state machines. In state machine terms, the main part of a +keyboard is one region and the number pad is another region. It would be a little inconvenient to handle two different -state machines as totally separate entities because in a sense they are -still working together in a sense. This is why orthogonal regions can -combine together a multiple simultaneous states within a single state +state machines as totally separate entities, because they +still work together in some fashion. This independence lets orthogonal regions +combine together in multiple simultaneous states within a single state in a state machine. [appendix] [[appendices-zookeeper]] == Distributed State Machine Technical Paper + This appendix provides more detailed technical documentation about -using a Zookeeper with a Spring State Machine. +using a Zookeeper instance with Spring Statemachine. === Abstract -Introducing a `distributed state` on top of a single state machine -instance running on a single `jvm` is a difficult and a complex topic. -`Distributed State Machine` is introducing a few relatively complex -problems on top of a simple state machine due to its run-to-completion -model and generally because of its single thread execution model, -though orthogonal regions can be executed parallel. One other natural -problem is that a state machine transition execution is driven by triggers + +Introducing a "`distributed state`" on top of a single state machine +instance running on a single JVM is a difficult and a complex topic. +The concept of a "`Distributed State Machine`" introduces a few relatively complex +problems on top of a simple state machine, due to its run-to-completion +model and, more generally, because of its single-thread execution model, +though orthogonal regions can be run in parallel. One other natural +problem is that state machine transition execution is driven by triggers, which are either `event` or `timer` based. -Distributed `Spring State Machine` is trying to solve problem of spanning -a generic `State Machine` through a jvm boundary. Here we show that a generic -`State Machine` concepts can be used in multiple `jvm's` and `Spring -Application Contexts`. +Spring State Machine tries to solve the problem of spanning +a generic "`State Machine`" through a JVM boundary by supporting distributed +state machines. Here we show that you can use generic +"`State Machine`" concepts across multiple JVMs and Spring +Application Contexts. -We found that if `Distributed State Machine` abstraction is carefully chosen +We found that, if `Distributed State Machine` abstraction is carefully chosen and backing distributed state repository guarantees `CP` readiness, it is -possible to create a consistent state machine which is able to share +possible to create a consistent state machine that can share distributed state among other state machines in an ensemble. -Our results demonstrate that distributed state changes are consistent if backing -repository is `CP`. We anticipate our distributed state machine to provide -a foundation to applications which need to work with a shared distributed -states. This model aims to provide a good methods for cloud applications -to have much easier ways to communicate with each others without having -a need to explicitly build these distributed state concepts. +Our results demonstrate that distributed state changes are consistent if the backing +repository is "`CP`" (discussed <>). +We anticipate our distributed state machine can provide +a foundation to applications that need to work with shared distributed +states. This model aims to provide good methods for cloud applications +to have much easier ways to communicate with each other without having +to explicitly build these distributed state concepts. + +[[state-machine-technical-paper-introduction]] +=== Introduction -=== Intro Spring State Machine is not forced to use a single threaded execution -model because once multiple regions are uses, regions can be executed -parallel if necessary configuration is applied. This is an important -topic because once user wants to have a parallel state machine -execution it will make state changes faster for independent regions. +model, because, once multiple regions are used, regions can be executed in +parallel if the necessary configuration is applied. This is an important +topic, because, once a user wants to have parallel state machine +execution, it makes state changes faster for independent regions. -When state changes are no longer driven by a trigger in a local jvm or +When state changes are no longer driven by a trigger in a local JVM or a local state machine instance, transition logic needs to be controlled externally in an arbitrary persistent storage. This storage needs to -have a ways to notify participating state machines when distributed +have a way to notify participating state machines when distributed state is changed. https://en.wikipedia.org/wiki/CAP_theorem[CAP Theorem] states that -"it is impossible for a distributed computer system to simultaneously -provide all three of the following guarantees, `consistency`, -`availability` and `partition tolerance` ". What this means is that -whatever is chosen for a backing persistence storage is it advisable -to be `CP`. In this context `CP` means `consistency` and `partition -tolerance`. Naturally `Distributed Spring Statemachine` doesn't care -about what is its `CAP` level but in reality `consistency` and -`partition tolerance` are more important than `availability`. This is -an exact reason why i.e. `Zookeeper` is a `CP` storage. +it is impossible for a distributed computer system to simultaneously +provide all three of the following guarantees: consistency, +availability, and partition tolerance. + +This means that, +whatever is chosen for a backing persistence storage, it is advisable +to be "`CP`". In this context, "`CP`" means "`consistency`" and "`partition +tolerance`". Naturally, a distributed Spring Statemachine does not care +about its "`CAP`" level but, in reality, "`consistency`" and +"`partition tolerance`" are more important than "`availability`". This is +an exact reason why (for example) Zookeeper uses "`CP`" storage. All tests presented in this article are accomplished by running custom -`jepsen` tests in a following environment: +Jepsen tests in the following environment: -* Cluster having nodes n1, n2, n3, n4 and n5. -* Each node have a `Zookeeper` instance constructing an ensemble with +* A cluster having nodes n1, n2, n3, n4 and n5. +* Each node has a `Zookeeper` instance that constructs an ensemble with all other nodes. -* Each node have a <> sample installed - which will connect to a local `Zookeeper` node. -* Every state machine instance will only communicate with a local - `Zookeeper` instance. While connecting machine to multiple instances +* Each node has a <> sample installed, + to connect to a local `Zookeeper` node. +* Every state machine instance communicates only with a local + `Zookeeper` instance. While connecting a machine to multiple instances is possible, it is not used here. -* All state machine instances when started will create a - `StateMachineEnsemble` using `Zookeeper` ensemble. -* Sample contains a custom rest api's which `jepsen` will use to send +* All state machine instances, when started, create a + `StateMachineEnsemble` by using a Zookeeper ensemble. +* Each sample contains a custom rest API, which Jepsen uses to send events and check particular state machine statuses. -All jepsen tests for `Spring Distributed Statemachine` are available from +All Jepsen tests for `Spring Distributed Statemachine` are available from https://github.com/spring-projects/spring-statemachine/tree/master/jepsen/spring-statemachine-jepsen[Jepsen Tests.] === Generic Concepts -One design decision of a `Distributed State Machine` was not to make -individual `State Machine` instance aware of that it is part of a -`distributed ensemble`. Because main functions and features of a -`StateMachine` can be accessed via its interface, it makes sense to -wrap this instance using a `DistributedStateMachine`, which simply + +One design decision of a `Distributed State Machine` was not to make each +individual state machine instance be aware that it is part of a +"`distributed ensemble`". Because the main functions and features of a +`StateMachine` can be accessed through its interface, it makes sense to +wrap this instance in a `DistributedStateMachine`, which intercepts all state machine communication and collaborates with an ensemble to orchestrate distributed state changes. One other important concept is to be able to persist enough -information from a state machine order to reset a state machine state -from arbitrary state into a new deserialized state. This is naturally -needed when a new state machine instance is joining with an ensemble -and it needs to synchronize its own internal state with a distributed +information from a state machine to reset a state machine state +from an arbitrary state into a new deserialized state. This is naturally +needed when a new state machine instance joins with an ensemble +and needs to synchronize its own internal state with a distributed state. Together with using concepts of distributed states and state -persisting it is possible to create a distributed state machine. -Currently only backing repository of a `Distributed State Machine` is -implemented using a `Zookeeper`. +persisting, it is possible to create a distributed state machine. +Currently, the only backing repository of a `Distributed State Machine` is +implemented by using Zookeeper. -As mentioned in <> distributed states are enabled by -wrapping an instance of a `StateMachine` within a -`DistributedStateMachine`. Specific `StateMachineEnsemble` -implementation is `ZookeeperStateMachineEnsemble` providing -integration with a `Zookeeper`. +As mentioned in <>, distributed states are enabled by +wrapping an instance of a `StateMachine` in a +`DistributedStateMachine`. The specific `StateMachineEnsemble` +implementation is `ZookeeperStateMachineEnsemble` provides +integration with Zookeeper. -=== ZookeeperStateMachinePersist -We wanted to have a generic interface `StateMachinePersist` which is -able to persist `StateMachineContext` into an arbitrary storage and -`ZookeeperStateMachinePersist` is implementing this interface for a +=== The Role of `ZookeeperStateMachinePersist` + +We wanted to have a generic interface (`StateMachinePersist`) that +Can persist `StateMachineContext` into arbitrary storage and +`ZookeeperStateMachinePersist` implements this interface for `Zookeeper`. -=== ZookeeperStateMachineEnsemble -While distributed state machine is using one set of serialized -contexts to update its own state, with zookeeper we're having a -conceptual problem how these context changes can be listened. We're -able to serialize context into a zookeeper `znode` and eventually -listen when `znode` data is modified. However `Zookeeper` doesn't -guarantee that you will get notification for every data change -because registered `watcher` for a `znode` is disabled once it fires -and user need to re-register that `watcher`. During this short time -a `znode` data can be changed thus resulting missing events. It is -actually very easy to miss these events by just changing data from a +=== The Role of `ZookeeperStateMachineEnsemble` + +While a distributed state machine uses one set of serialized +contexts to update its own state, with zookeeper, we have a +conceptual problem around how to listen to these context changes. We +can serialize context into a zookeeper `znode` and eventually +listen when the `znode` data is modified. However, `Zookeeper` does not +guarantee that you get a notification for every data change, +because a registered `watcher` for a `znode` is disabled once it fires +and the user need to re-register that `watcher`. During this short time, +a `znode` data can be changed, thus resulting in missing events. It is +actually very easy to miss these events by changing data from multiple threads in a concurrent manner. -Order to overcome this issue we're keeping individual context changes -in a multiple `znodes` and we just use a simple integer counter to mark -which `znode` is a current active one. This allows us to replay missed -events. We don't want to create more and more znodes and then later -delete old ones, instead we're using a simple concept of a circular -set of znodes. This allows to use predefined set of znodes where -a current can be determined with a simple integer counter. We already have -this counter by tracking main `znode` data version which in -`Zookeeper` is -an integer. +To overcome this issue, we keep individual context changes +in multiple `znodes` and we use a simple integer counter to mark +which `znode` is the current active one. Doing so lets us replay missed +events. We do not want to create more and more znodes and then later +delete old ones. Instead, we use the simple concept of a circular +set of znodes. This lets us use a predefined set of znodes where +the current node can be determined with a simple integer counter. We already have +this counter by tracking the main `znode` data version (which, in +`Zookeeper`, is an integer). -Size of a circular buffer is mandated to be a power of two not to get -trouble when integer is going to overflow thus we don't need to +The size of a circular buffer is mandated to be a power of two, to avoid +trouble when the integer goes to overflow. For this reason, we need not handle any specific cases. === Distributed Tolerance -Order to show how a various distributed actions against a state -machine work in a real life, we're using a set of `jepsen` tests to -simulate various conditions which may happen in a real distributed -cluster. These include a `brain split` on a network level, parallel -events with a multiple `distributed state machines` and changes in -an `extended state variables`. Jepsen tests are based on a sample -<> where this sample instance is run on -multiple hosts together with a `Zookeeper` instance on every node -where state machine is run. Essentially every state machine sample -will connect to local `Zookeeper` instance which allows use, via -`jepsen` to simulate network conditions. -Plotted graphs below in this chapter contain states and events which -directly maps to a state chart which can be found from +To show how a various distributed actions against a state +machine work in real life, we use a set of Jepsen tests to +simulate various conditions that might happen in a real distributed +cluster. These include a "`brain split`" on a network level, parallel +events with multiple "`distributed state machines`", and changes in +"`extended state variables`". Jepsen tests are based on a sample +<>, where this sample instance runs on +multiple hosts together with a Zookeeper instance on every node +where the state machine is run. Essentially, every state machine sample +connects to a local Zookeeper instance, which lets us, by using +Jepsen, to simulate network conditions. + +The plotted graphs shown later in this chapter contain states and events that +directly map to a state chart, which you can be find in <>. [[sm-tech-isolated-events]] ==== Isolated Events + Sending an isolated single event into exactly one state machine in an -ensemble is the most simplest testing scenario and demonstrates that a +ensemble is the simplest testing scenario and demonstrates that a state change in one state machine is properly propagated into other state machines in an ensemble. -In this test we will demonstrate that a state change in one machine -will eventually cause a consistent state change in other machines. +In this test, we demonstrate that a state change in one machine +eventually causes a consistent state change in other machines. +The following image shows the events and state changes for a test state machine: image::images/sm-tech-isolated-events.png[width=500] -What's happening in above chart: +In the preceding image: * All machines report state `S21`. * Event `I` is sent to node `n1` and all nodes report state change @@ -529,166 +578,169 @@ What's happening in above chart: from `S211` to `S212`. * Event `K` is sent to node `n3` and all nodes report state change from `S212` to `S21`. -* We cycle events `I`, `C`, `I` and `K` one more time via random nodes. +* We cycle events `I`, `C`, `I`, and `K` one more time, through random nodes. ==== Parallel Events -Logical problem with multiple distributed state machines is that if a -same event is sent into a multiple state machine exactly at a same -time, only one of those events will cause a distributed state -transitions. This is somewhat expected scenario because a first state -machine, for this event, which is able to change a distributed state -will control the distributed transition logic. Effectively all other -machines receiving this same event will silently discard the event -because distributed state is no longer in a state where particular + +One logical problem with multiple distributed state machines is that, if the +same event is sent into multiple state machines at exactly the same +time, only one of those events causes a distributed state +transitions. This is a somewhat expected scenario, because the first state +machine (for this event) that is able to change a distributed state +controls the distributed transition logic. Effectively, all other +machines that receive this same event silently discard the event, +because the distributed state is no longer in a state where a particular event can be processed. -In this test we will demonstrate that a state change caused by a -parallel events throughout an ensemble will eventually cause a -consistent state change in all machines. +In the test shown in the following image, we demonstrate that a state change caused by a +parallel event throughout an ensemble eventually causes a +consistent state change in all machines: image::images/sm-tech-parallel-events.png[width=500] -What's happening in above chart: - -* We use exactly same event flow than in previous sample - <> with a difference that events are always - sent to all nodes. +In the preceding image, we use the same event flow that we used in the previous sample +(<>), with the difference that events are always +sent to all nodes. ==== Concurrent Extended State Variable Changes + Extended state machine variables are not guaranteed to be atomic at -any given time but after a distributed state change, all state machines +any given time, but, after a distributed state change, all state machines in an ensemble should have a synchronized extended state. -In this test we will demonstrate that a change in extended state -variables in one distributed state machine will eventually be -consistent in all distributed state machines. +In this test, we demonstrate that a change in extended state +variables in one distributed state machine eventually becomes +consistent in all the distributed state machines. +The following image shows this test: image::images/sm-tech-isolated-events-with-variable.png[width=500] -What's happening in above chart: +In the preceding image: * Event `J` is send to node `n5` with event variable `testVariable` - having value `v1`. All nodes are then reporting having variable - `testVariable` as value `v1`. -* Event `J` is repeated from variable `v2` to `v8` doing same checks. + having value `v1`. All nodes then report having a variable named + `testVariable` with a value of `v1`. +* Event `J` is repeated from variable `v2` to `v8`, doing the same checks. ==== Partition Tolerance -We need to always assume that sooner or later things in a cluster will -go bad whether it is just a crash of a `Zookeeper` instance, a state -machine or a network problem like a `brain split`. Brain split is a -situation where existing cluster members are isolated so that only -part of a hosts are able to see each others. Usual scenario is that a -brain split will create a minority and majority partitions of an -ensemble where hosts in a minority cannot participate in an ensemble -anymore until network status has been healed. -In below tests we will demonstrate that various types of brain-split's in -an ensemble will eventually cause fully synchronized state of all +We need to always assume that, sooner or later, things in a cluster +go bad, whether it is a crash of a Zookeeper instance, a state +machine crash, or a network problem such as a "`brain split`". (A brain split is a +situation where existing cluster members are isolated so that only +parts of hosts are able to see each other). The usual scenario is that a +brain split creates minority and majority partitions of an +ensemble such that hosts in the minority cannot participate in an ensemble +until the network status has been healed. + +In the following tests, we demonstrate that various types of brain split in +an ensemble eventually cause a fully synchronized state of all the distributed state machines. -There are two scenarios having a one straight brain split in a +There are two scenarios that have a straight brain split in a network where where `Zookeeper` and `Statemachine` instances are -split in half, assuming each `Statemachine` will connect into a -local `Zookeeper` instance: +split in half (assuming each `Statemachine` connects to a +local `Zookeeper` instance): -* If current zookeeper leader is kept in a majority, all clients - connected into majority will keep functioning properly. -* If current zookeeper leader is left in minority, all clients will - disconnect from it and will try to connect back till previous - minority members has successfully joined back to existing majority +* If the current zookeeper leader is kept in a majority, all clients + connected to the majority keep functioning properly. +* If the current zookeeper leader is left in the minority, all clients + disconnect from it and try to connect back till previous + minority members have successfully joined back to existing majority ensemble. -[NOTE] -==== -In our current `jepsen` tests we can't separate zookeeper split brains -scenarios between leader left in majority or minority so we need to -run tests multiple time to accomplish this situation. -==== +NOTE: In our current Jepsen tests, we cannot separate Zookeeper split-brain +scenarios between the leader being left in the majority or in the minority, so we need to +run the tests multiple times to accomplish this situation. -[NOTE] -==== -In below plots we have mapped a state machine error state into an -`error` to indicate that `state machine` is in error state instead or -a normal state. Please indicate this when interpreting chart states. -==== +NOTE: In the following plots, we have mapped a state machine error state into an +`error` to indicate that the state machine is in an error state instead of +a normal state. Please remember this when interpreting chart states. -In this first test we show that when existing zookeeper leader was -kept in majority, 3 out of 5 machines will continue as is. +In this first test, we show that, when an existing Zookeeper leader was +kept in the majority, three out of five machines continue as is. +The following image shows this test: image::images/sm-tech-partition-half-1.png[width=500] -What's happening in above chart: -* First event `C` is sent to all machine leading a state change to +In the preceding image: + +* The first event, `C`, is sent to all machines, leading a state change to `S211`. -* Jepsen nemesis will cause a brain-split which is causing partitions - of `n1/n2/n5` and `n3/n4`. Nodes `n3/n4` are left in minority and - nodes `n1/n2/n5` construct a new healthy majority. Nodes in - majority will keep function without problems but nodes in minority - will get into error state. -* Jepsen will heal network and after some time nodes `n3/n4` will join - back into ensemble and synchronize its distributed status. -* Lastly event `K1` is sent to all state machines to ensure that ensemble - is working properly. This state change will lead back to state +* Jepsen nemesis causes a brain split, which causes partitions + of `n1/n2/n5` and `n3/n4`. Nodes `n3/n4` are left in the minority, and + nodes `n1/n2/n5` construct a new healthy majority. Nodes in the + majority keep functioning without problems, but nodes in the minority + go into error states. +* Jepsen heals the network and, after some time, nodes `n3/n4` join + back into the ensemble and synchronize its distributed status. +* Finally, event `K1` is sent to all state machines to ensure that the ensemble + is working properly. This state change leads back to state `S21`. -In this second test we show that when existing zookeeper leader was -kept in minority, all machines will error out: +In the second test, we show that, when the existing zookeeper leader was +kept in the minority, all machines error out. +The following image shows the second test: image::images/sm-tech-partition-half-2.png[width=500] -What's happening in above chart: -* First event `C` is sent to all machine leading a state change to +In the preceding image: + +* The first event, `C`, is sent to all machines leading to a state change to `S211`. -* Jepsen nemesis will cause a brain-split which is causing partitions - so that existing `Zookeeper` leader is kept in minority and all - instances are disconnected from ensemble. -* Jepsen will heal network and after some time all nodes will join - back into ensemble and synchronize its distributed status. -* Lastly event `K1` is sent to all state machines to ensure that ensemble - is working properly. This state change will lead back to state +* Jepsen nemesis causes a brain split, which causes partitions + such that the existing `Zookeeper` leader is kept in the minority and all + instances are disconnected from the ensemble. +* Jepsen heals the network and, after some time, all nodes join + back into the ensemble and synchronize its distributed status. +* Finally, event `K1` is sent to all state machines to ensure that ensemble + workS properly. This state change leads back to state `S21`. ==== Crash and Join Tolerance -In this test we will demonstrate that killing existing state machine -and then joining new instance back into an ensemble will keep the -distributed state healthy and newly joined state machines will synchronize + +In this test, we demonstrate that killing an existing state machine +and then joining a new instance back into an ensemble keeps the +distributed state healthy and the newly joined state machines synchronize their states properly. +The following image shows the crash and join tolerance test: image::images/sm-tech-stop-start.png[width=500] -[NOTE] -==== -In this test, states are not checked between first `X` and last `X`, -thus graph will will show flat line in between. States are checked -exactly where state change happens between `S21` and `S211`. -==== +NOTE: In this test, states are not checked between first the `X` and last the `X`. +Thus, the graph shows a flat line in between. The states are checked +exactly where the state change happens between `S21` and `S211`. -What's happening in above chart: +In the preceding image: -* All state machines are transitioned from initial state `S21` into - `S211` so that we can test proper state synchronize during join. -* `X` is marking when a specific node has been crashed and started. -* At a same time we request states from all machines and plot it. -* Finally we do a simple transition back to `S21` from `S211` to make - sure that all state machines are still functioning properly. +* All state machines are transitioned from the initial state (`S21`) into + state `S211` so that we can test proper state synchronize during the join. +* `X` marks when a specific node has been crashed and started. +* At the same time, we request states from all machines and plot the result. +* Finally, we do a simple transition back to `S21` from `S211` to make + sure that all state machines still function properly. [[devdocs]] == Developer Documentation -This appendix provides generic information for a developers who may + +This appendix provides generic information for adevelopers who may want to contribute or other people who want to understand how state -machine works or what are its internal concepts. +machine works or understand its internal concepts. [[devdocs-configmodel]] === StateMachine Config Model + `StateMachineModel` and other related SPI classes are an abstraction between various configuration and factory classes. This also allows easier integration for others to build state machines. -As shown above a state machine can be instantiated by building a model -using configuration data classes and then asking a factory to build a -state machine. +As the following listing shows, you can instantiate a state machine by building a model +with configuration data classes and then asking a factory to build a +state machine: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests6.java[tags=snippetA] ---- +==== diff --git a/docs/src/reference/asciidoc/faq.adoc b/docs/src/reference/asciidoc/faq.adoc index 4016c8d2..1c0c762a 100644 --- a/docs/src/reference/asciidoc/faq.adoc +++ b/docs/src/reference/asciidoc/faq.adoc @@ -1,37 +1,32 @@ [[statemachine-faq]] = FAQ -This chapter tries to give solutions to question user is most likely -to ask. + +This chapter answers the questions that Spring Statemachine users most often ask. == State Changes -.I want to transit to next state automatically +.How can I automatically transit to the next state? -{zwsp} + +You can choose from three approaches: -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 an event which will be deferred and thus causing next +* Implement an action and send an appropriate event to a state machine + to trigger a transition into the proper target state. +* Define a deferred event within a state and, before sending an event, + send another event that is deferred. Doing so causes the 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 +* Implement a triggerless transition, which automatically causes a + state transition into the next state when state is entered and its actions has been completed. == Extended State -.How I can initialise variables on state machine start +.How I can initialize 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 +An important concept in a state machine is that nothing really happens +unless a trigger causes a state transition that 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 +always has an initial transition when a state machine is started. With +this initial transition, you can run a simple action that, within +a `StateContext`, can do whatever it likes with extended state variables. - diff --git a/docs/src/reference/asciidoc/getting-started.adoc b/docs/src/reference/asciidoc/getting-started.adoc index 6081ac1b..04ffbdc3 100644 --- a/docs/src/reference/asciidoc/getting-started.adoc +++ b/docs/src/reference/asciidoc/getting-started.adoc @@ -1,77 +1,83 @@ [[statemachine-getting-started]] = Getting started -If you’re just getting started with Spring Statemachine, -this is the section for you! Here we answer the basic -“what?”, “how?” and “why?” questions. You’ll find a gentle -introduction to Spring Statemachine. We’ll then build our -first Spring Statemachine application, discussing some + +If you are just getting started with Spring Statemachine, +this is the section for you! Here, we answer the basic +“`what?`”, “`how?`” and “`why?`” questions. We start with a gentle +introduction to Spring Statemachine. We then build our +first Spring Statemachine application and discuss some core principles as we go. -== System Requirements +== System Requirement + Spring Statemachine {revnumber} is built and tested with -JDK 8(all artifacts have JDK 7 compatibility) and Spring -Framework {spring-version} and doesn't require any other +JDK 8 (all artifacts have JDK 7 compatibility) and Spring +Framework {spring-version}. It does not require any other dependencies outside of Spring Framework within its core system. -Other optional parts like <> has dependencies to -a `Zookeeper`, while <> has dependencies -to spring-shell and spring-boot which pulls other dependencies -beyond framework itself. Also optional security and data access has -dependencies to _Spring Security_ and _Spring Data Modules_. +Other optional parts (such as <>) have dependencies on +Zookeeper, while <> has dependencies +on `spring-shell` and `spring-boot`, which pull other dependencies +beyond the framework itself. Also, the optional security and data access features have +dependencies to on Spring Security and Spring Data modules. == Modules -The following modules are available for Spring Statemachine. + +The following table describes the modules that are available for Spring Statemachine. |=== |Module |Description -|spring-statemachine-core -|Core system of a Spring Statemachine. +|`spring-statemachine-core` +|The core system of Spring Statemachine. -|spring-statemachine-recipes-common -|Common recipes which doesn't require dependencies outside of a core +|`spring-statemachine-recipes-common` +|Common recipes that do not require dependencies outside of the core framework. -|spring-statemachine-kryo -|`Kryo` serializers for state machine. +|`spring-statemachine-kryo` +|`Kryo` serializers for Spring Statemachine. -|spring-statemachine-data-common +|`spring-statemachine-data-common` |Common support module for `Spring Data`. -|spring-statemachine-data-jpa +|`spring-statemachine-data-jpa` |Support module for `Spring Data JPA`. -|spring-statemachine-data-redis +|`spring-statemachine-data-redis` |Support module for `Spring Data Redis`. -|spring-statemachine-data-mongodb +|`spring-statemachine-data-mongodb` |Support module for `Spring Data MongoDB`. -|spring-statemachine-zookeeper -|`Zookeeper` integration for a distributed state machine. +|`spring-statemachine-zookeeper` +|Zookeeper integration for a distributed state machine. -|spring-statemachine-test +|`spring-statemachine-test` |Support module for state machine testing. -|spring-statemachine-cluster +|`spring-statemachine-cluster` |Support module for Spring Cloud Cluster. +Note that Spring Cloud Cluster has been superseded by Spring Integration. -|spring-statemachine-uml -|Support module for UI uml modeling with Eclipse Papyrus. +|`spring-statemachine-uml` +|Support module for UI UML modeling with Eclipse Papyrus. -|spring-statemachine-autoconfigure -|Support module for `Spring Boot`. +|`spring-statemachine-autoconfigure` +|Support module for Spring Boot. -|spring-statemachine-bom +|`spring-statemachine-bom` |Bill of Materials pom. -|spring-statemachine-starter -|`Spring Boot` starter. +|`spring-statemachine-starter` +|Spring Boot starter. |=== == Using Gradle -Here is a typical `build.gradle` file created by https://start.spring.io: +The following listing shows a typical `build.gradle` file created by choosing various settings at https://start.spring.io: + +==== [source,groovy,indent=0,subs="attributes+"] ---- buildscript { @@ -119,29 +125,29 @@ dependencyManagement { } } ---- - -[NOTE] -==== -Replace `0.0.1-SNAPSHOT` with a version you want to use. ==== -Having a normal project structure you'd build this with command: +IMPORTANT: Replace `0.0.1-SNAPSHOT` with a version you want to use. + +With a normal project structure, you can build this project with the following command: + +==== [source,text,indent=0] ---- # ./gradlew clean build ---- - -Expected Spring Boot packaged fat-jar would be `build/libs/demo-0.0.1-SNAPSHOT.jar`. - -[NOTE] ==== -You don't need repos `libs-milestone` and `libs-snapshot` for + +The expected Spring Boot-packaged fat jar would be `build/libs/demo-0.0.1-SNAPSHOT.jar`. + +NOTE: You do not need the`libs-milestone` and `libs-snapshot` repositories for production development. -==== == Using Maven -Here is a typical `pom.xml` file created by https://start.spring.io: +The following example shows a typical `pom.xml` file, which was created by choosing various options at https://start.spring.io: + +==== [source,xml,indent=0,subs="attributes+"] ---- @@ -246,30 +252,30 @@ Here is a typical `pom.xml` file created by https://start.spring.io: ---- - -[NOTE] -==== -Replace `0.0.1-SNAPSHOT` with a version you want to use. ==== -Having a normal project structure you'd build this with command: +IMPORTANT: Replace `0.0.1-SNAPSHOT` with a version you want to use. + +With a normal project structure, you can build this project with the following command: + +==== [source,text,indent=0] ---- # mvn clean package ---- - -Expected Spring Boot packaged fat-jar would be `target/demo-0.0.1-SNAPSHOT.jar`. - -[NOTE] ==== -You don't need repos `libs-milestone` and `libs-snapshot` for + +The expected Spring Boot-packaged fat-jar would be `target/demo-0.0.1-SNAPSHOT.jar`. + +NOTE: You do not need the `libs-milestone` and `libs-snapshot` repositories for production development. + +== Developing Your First Spring Statemachine Application + +You can start by creating a simple Spring Boot `Application` class +that implements `CommandLineRunner`. The following example shows how to do so: + ==== - -== Developing your first Spring Statemachine application -Let's start by creating a simple Spring Boot `Application` class -implementing `CommandLineRunner`. - [source,java,indent=0] ---- @SpringBootApplication @@ -281,8 +287,11 @@ public class Application implements CommandLineRunner { } ---- +==== -Add states and events: +Then you need to add states and events, as the following example shows: + +==== [source,java,indent=0] ---- public enum States { @@ -293,8 +302,11 @@ public enum Events { E1, E2 } ---- +==== -Add state machine configuration: +Then you need to add state machine configuration, as the following example shows: + +==== [source,java,indent=0] ---- @Configuration @@ -342,8 +354,12 @@ public class StateMachineConfig } } ---- +==== -Implement `CommandLineRunner`, autowire `StateMachine`: +Then you need to implement `CommandLineRunner` and autowire `StateMachine`. +The following example shows how to do so: + +==== [source,java,indent=0] ---- @Autowired @@ -355,18 +371,23 @@ public void run(String... args) throws Exception { stateMachine.sendEvent(Events.E2); } ---- +==== -Depending whether you build your application using `Gradle` or `Maven` -it's run `java -jar build/libs/gs-statemachine-0.1.0.jar` or -`java -jar target/gs-statemachine-0.1.0.jar` respectively. +Depending on whether you build your application with `Gradle` or `Maven`, +you can run it by using `java -jar build/libs/gs-statemachine-0.1.0.jar` or +`java -jar target/gs-statemachine-0.1.0.jar`, respectively. -What is expected for running this command is a normal Spring Boot output -but if you look closely you see lines: +The result of this command should be normal Spring Boot output. +However, you should also find the following lines: +==== [source,text,indent=0] ---- State change to SI State change to S1 State change to S2 ---- +==== +These lines indicate that the machine you constructed +is moving from one state to another, as it should. diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc index 54eb44cb..72cf17d3 100644 --- a/docs/src/reference/asciidoc/index.adoc +++ b/docs/src/reference/asciidoc/index.adoc @@ -17,11 +17,19 @@ = Spring Statemachine - Reference Documentation include::preface.adoc[] + include::introduction.adoc[] + include::getting-started.adoc[] + include::whatsnew.adoc[] + include::sm.adoc[] + include::recipes.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 de0fe80e..ad44cb10 100644 --- a/docs/src/reference/asciidoc/introduction.adoc +++ b/docs/src/reference/asciidoc/introduction.adoc @@ -1,60 +1,62 @@ [[introduction]] = Introduction -Spring Statemachine(SSM) is a framework for application developers to -use traditional state machine concepts with Spring applications. SSM -aims to provide the following features: -* Easy to use flat one level state machine for simple use cases. +Spring Statemachine (SSM) is a framework that lets application developers +use traditional state machine concepts with Spring applications. SSM +provides the following features: + +* Easy-to-use flat (one-level) state machine for simple use cases. * Hierarchical state machine structure to ease complex state configuration. * State machine regions to provide even more complex state configurations. -* Usage of triggers, transitions, guards and actions. -* Type safe configuration adapter. +* Usage of triggers, transitions, guards, and actions. +* Type-safe configuration adapter. * State machine event listeners. -* Spring IOC integration to associate beans with a state machine. +* Spring IoC integration to associate beans with a state machine. -Before you continue it's worth to go through appendices <> -and <> to get a generic idea of what state machines are, -mostly because the rest of the documentation expects the reader to be -fairly familiar with state machine concepts. +Before you continue, we recommend going through the appendices <> +and <> to get a generic idea of what state machines are. +The rest of the documentation expects you to be +familiar with state machine concepts. == Background -State machines are powerful because behaviour is always guaranteed to be + +State machines are powerful because their behavior is always guaranteed to be consistent and relatively easily debugged due to how operational -rules are written in stone when machine is started. The 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. What -will drive a state machine are triggers, which are either based on +rules are written in stone when a machine is started. The idea is that your +application is now in and may exist in a finite number of states. Then something +happens that takes your application from one state to the next. +A state machine is driven by triggers, which are based on either events or timers. -It is much easier to design high level logic outside of your +It is much easier to design high-level logic outside of your application and then interact with a state machine in various -different ways. You can simply interact with a state machine by -sending events, listening to what a state machine does or simply requesting +different ways. You can interact with a state machine by +sending events, listening to what a state machine does, or requesting the current state. -Traditionally state machines are added to an existing project when +Traditionally, state machines are added to an existing project when developers realize that the code base is starting to look like a plate full of spaghetti. Spaghetti code looks like a never ending, hierarchical -structure of IFs, ELSEs and BREAK clauses and probably compilers should +structure of IF, ELSE, and BREAK clauses, and compilers should probably ask developers to go home when things are starting to look too complex. == Usage Scenarios -A project is a good candidate to use a state machine if: +A project is a good candidate to use a state machine when: -* The application or part of its structure can be represented as states. +* You can represent the application or part of its structure as states. * You want to split complex logic into smaller manageable tasks. -* The application is already suffering concurrency issues with i.e. +* The application is already suffering concurrency issues with (for example) something happening asynchronously. -You are already trying to implement a state machine if: +You are already trying to implement a state machine when you: -* Using boolean flags or enums to model situations. -* Having variables which only have meaning for some part of your +* Use boolean flags or enums to model situations. +* Have variables that have meaning only for some part of your application lifecycle. -* Looping through if/else structure and checking if a particular flag or - enum is set and then making further exceptions about what to do when certain - combinations of your flags and enums exist or don't exist together. - +* Loop through an if-else structure (or, worse, multiple such structures), + check whether a particular flag or + enum is set, and then make further exceptions about what to do when certain + combinations of your flags and enums exist or do not exist. diff --git a/docs/src/reference/asciidoc/preface.adoc b/docs/src/reference/asciidoc/preface.adoc index 24edf4c3..e67872c1 100644 --- a/docs/src/reference/asciidoc/preface.adoc +++ b/docs/src/reference/asciidoc/preface.adoc @@ -1,24 +1,25 @@ [preface] == Preface -The concept of a state machine is most likely older that any reader + +The concept of a state machine is most likely older than any reader of this reference documentation and definitely older than the Java language itself. Description of finite automata dates back to 1943 when gentlemen Warren McCulloch and Walter Pitts wrote a paper about -it. Later George H. Mealy presented a state machine concept in 1955 -which is known as a Mealy Machine. A year later in 1956 Edward F. -Moore presented another paper which is known as a Moore Machine. If -you've ever read anything about state machines, the names Mealy and Moore +it. Later George H. Mealy presented a state machine concept (known as +a "`Mealy Machine`") in 1955. A year later, in 1956, Edward F. +Moore presented another paper, in which he described what is known as +a "`Moore Machine`". If +you have ever read anything about state machines, the names, Mealy and Moore, should have popped up at some point. -This reference documentation contains the following parts. +This reference documentation contains the following parts: -<> introduction to this reference documentation +<> contains introduction to this reference documentation. -<> describes the usage of Spring Statemachine(SSM) +<> describes the usage of Spring Statemachine(SSM). -<> more detailed state machine examples +<> contains more detailed state machine examples. -<> frequently asked questions - -<> generic info about used material and state machines +<> contains frequently asked questions. +<> contains generic information about used material and state machines. diff --git a/docs/src/reference/asciidoc/recipes.adoc b/docs/src/reference/asciidoc/recipes.adoc index 75ac313e..307b128d 100644 --- a/docs/src/reference/asciidoc/recipes.adoc +++ b/docs/src/reference/asciidoc/recipes.adoc @@ -1,130 +1,145 @@ [[statemachine-recipes]] = Recipes + This chapter contains documentation for existing built-in state machine recipes. -What exactly is a recipe? As Spring Statemachine is always going to be -a foundational framework meaning that its core will not have that much -higher level functionality or dependencies outside of a Spring -Framework. Correct usage of a state machine may be a little difficult -time to time and there's always some common use cases how state -machine can be used. Recipe modules are meant to provide a higher -level solutions to these common use cases and also provide examples -beyond samples how framework can be used. +Spring Statemachine is a foundational framework. That is, it does not have much +higher-level functionality or many dependencies beyond Spring Framework. +Consequently, correctly using a state machine may be difficult. To help, +we have created a set of recipe modules that address common use cases. -[NOTE] -==== -Recipes are a great way to make external contributions this Spring -Statemachine project. If you're not ready to contribute to the +What exactly is a recipe? A state machine recipe is a module that addresses a common +use case. In essence, a state machine recipe is both an example that we have tried to +make it easy for you to reuse and extend. + +NOTE: Recipes are a great way to make external contributions to the Spring +Statemachine project. If you are not ready to contribute to the framework core itself, a custom and common recipe is a great way to -share functionality among other users. -==== +share functionality with other users. [[statemachine-recipes-persist]] == Persist -Persist recipe is a simple utility which allows to use a single state -machine instance to persist and update a state of an arbitrary item in + +The persist recipe is a simple utility that lets you use a single state +machine instance to persist and update the state of an arbitrary item in a repository. -Recipes main class is `PersistStateMachineHandler` which assumes user -to do three different things: +The recipe's main class is `PersistStateMachineHandler`, which makes three assumptions: -- An instance of a `StateMachine` needs to be used - with a `PersistStateMachineHandler`. States and Events are required - to be type of Strings. -- `PersistStateChangeListener` need to be registered with handler - order to react to persist request. -- Method `handleEventWithState` is used to orchestrate state changes. +* An instance of a `StateMachine` needs to be used + with a `PersistStateMachineHandler`. Note that states and Events are required + to be type of `String`. +* `PersistStateChangeListener` needs to be registered with handler + to react to persist request. +* The `handleEventWithState` method is used to orchestrate state changes. -There is a sample demonstrating usage of this recipe at +You can find a sample that shows how to use this recipe at <>. [[statemachine-recipes-tasks]] == Tasks -Tasks recipe is a concept to execute DAG of `Runnable` instances using -a state machine. This recipe has been developed from ideas introduced -in sample <>. -Generic concept of a state machine is shown below. In this state chart -everything under `TASKS` just shows a generic concept of how a single -task is executed. Because this recipe allows to register deep -hierarchical DAG of tasks, meaning a real state chart would be deep -nested collection of sub-states and regions, there's no need to be +The tasks recipe is a concept to run DAG (Directed Acrylic Graph) of `Runnable` instances that use +a state machine. This recipe has been developed from ideas introduced +in <> sample. + +The next image shows the generic concept of a state machine. In this state chart, +everything under `TASKS` shows a generic concept of how a single +task is executed. Because this recipe lets you register a deep +hierarchical DAG of tasks (meaning a real state chart would be a deeply +nested collection of sub-states and regions), we have no need to be more precise. -For example if you have only two registered tasks, below state chart -would be correct with `TASK_id` replaced with `TASK_1` and `TASK_2` if -registered tasks ids are `1` and `2`. +For example, if you have only two registered tasks, the following state chart +would be correct when `TASK_id` is replaced with `TASK_1` and `TASK_2` (assuming +the registered tasks IDs are `1` and `2`). image::images/statechart9.png[width=500] -Executing a `Runnable` may result an error and especially if a complex -DAG of tasks is involved it is desirable that there is a way to handle -tasks execution errors and then having a way to continue execution -without executing already successfully executed tasks. Addition to -this it would be nice if some execution errors can be handled -automatically and as a last fallback, if error can't be handled -automatically, state machine is put into a state where user can handle +Executing a `Runnable` may result an error. Especially if a complex +DAG of tasks is involved, you want to have a way to handle +task execution errors and then have a way to continue execution +without executing already successfully executed tasks. Also, +it would be nice if some execution errors can be handled +automatically. As a last fallback, if an error cannot be handled +automatically, the state machine is put into a state where the user can handle errors manually. -`TasksHandler` contains a builder method to configure handler instance -and follows a simple builder pattern. This builder can be used to -register `Runnable` tasks, `TasksListener` instances, define -`StateMachinePersist` hook, and setup custom `TaskExecutor` instance. +`TasksHandler` contains a builder method to configure a handler instance +and follows a simple builder pattern. You can use this builder to +register `Runnable` tasks and `TasksListener` instances, define +`StateMachinePersist` hook, and set up custom `TaskExecutor` instance. -Now let's take a simple `Runnable` just doing a simple sleep as shown -below. This is a base of all examples in this chapter. +Now we can take a simple `Runnable` that runs a simple sleep as the following +example shows: +==== [source,java,indent=0] ---- include::samples/DocsTasksSampleTests.java[tags=snippetAA] ---- +==== -To execute multiple `sleepRunnable` tasks just register tasks and -execute `runTasks()` method from `TasksHandler`. +NOTE: The preceding example is the base for all of the examples in this chapter. +To execute multiple `sleepRunnable` tasks, you can register tasks and +execute `runTasks()` method from `TasksHandler`, as the following example shows: + +==== [source,java,indent=0] ---- include::samples/DocsTasksSampleTests.java[tags=snippetB] ---- +==== -Order to listen what is happening with a task execution an instance of -a `TasksListener` can be registered with a `TasksHandler`. Recipe -provides an adapter `TasksListenerAdapter` if you don't want to -implement a full interface. Listener provides a various hooks to -listen tasks execution events. +To listen to what is happening with a task execution, you can register an instance of +a `TasksListener` with a `TasksHandler`. This recipe +provides an adapter `TasksListenerAdapter` if you do not want to +implement a full interface. The listener provides a various hooks to +listen tasks execution events. The following example shows the definition of the +`MyTasksListener` class: +==== [source,java,indent=0] ---- include::samples/DocsTasksSampleTests.java[tags=snippetAB] ---- +==== -Listeners can be either registered via a builder or directly with a -`TasksHandler` as shown above. +You can either register listeners by using a builder or register them directly with a +`TasksHandler` as the following example shows: +==== [source,java,indent=0] ---- include::samples/DocsTasksSampleTests.java[tags=snippetC] ---- +==== -Above sample show how to create a deep nested DAG of tasks. Every task -needs to have an unique identifier and optionally as task can be -defined to be a sub-task. Effectively this will create a DAG of tasks. +Every task +needs to have a unique identifier, and (optionally) a task can be +defined to be a sub-task. Effectively, this creates a DAG of tasks. +The following example shows how to create a deep nested DAG of tasks: +==== [source,java,indent=0] ---- include::samples/DocsTasksSampleTests.java[tags=snippetD] ---- +==== -When error happens and a state machine running these tasks goes into a -`ERROR` state, user can call handler methods `fixCurrentProblems` to -reset current state of tasks kept in a state machine extended state -variables. Handler method `continueFromError` can then be used to -instruct state machine to transition from `ERROR` state back to -`READY` state where tasks can be executed again. +When an error happens and the state machine running these tasks goes into an +`ERROR` state, you can call `fixCurrentProblems` handler method to +reset the current state of the tasks kept in the state machine's extended state +variables. You can then use the `continueFromError` handler method to +instruct the state machine to transition from the `ERROR` state back to the +`READY` state, where you can again run tasks. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsTasksSampleTests.java[tags=snippetE] ---- - +==== diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index dd216a39..08455b20 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -2,97 +2,113 @@ = State Machine Examples This part of the reference documentation explains the use of state -machines together with a sample code and 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. +machines together with sample code and UML state charts. We use a few +shortcuts when representing the relationship between a state chart, Spring Statemachine +configuration, and what an application does with a state machine. For +complete examples, you should study the samples repository. -Samples are build directly from a main source distribution during a -normal build cycle. Samples in this chapter are: +Samples are built directly from a main source distribution during a +normal build cycle. This chapter includes the following samples: -<> Turnstile. +<> -<> Showcase. +<> -<> CD Player. +<> -<> Tasks. +<> -<> Washer. +<> -<> Persist. +<> -<> Zookeeper. +<> -<> Web. +<> -<> Scope. +<> -<> Security. +<> -<> Event Service. +<> -<> Deploy. +<> -<> Order Shipping. +<> -<> JPA Config. +<> -<> Data Persist. +<> -<> Data Multi Persist. +<> -<> Monitoring. +<> +The following listing shows how to build the samples: -[source,text] +==== +[source,bash] ---- ./gradlew clean build -x test ---- +==== Every sample is located in its own directory under -`spring-statemachine-samples`. Samples are based on spring-boot and -spring-shell and you will find usual boot fat jars under every sample -projects `build/libs` directory. +`spring-statemachine-samples`. The samples are based on Spring Boot and +Spring Shell, and you can find the usual Boot fat jars under every sample +project's `build/libs` directory. -[NOTE] -==== -Filenames for jars we refer in this section are populated during a -build of this document, meaning if you're building samples from a -master, you have files with `BUILD-SNAPSHOT` postfix. -==== +NOTE: The filenames for the jars to which we refer in this section are populated during a +build of this document, meaning that, if you build samples from +master, you have files with a `BUILD-SNAPSHOT` postfix. [[statemachine-examples-turnstile]] == Turnstile -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 -simplest 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. +Turnstile is a simple device that gives you access if payment is +made. It is a concept that is simple to model using a state machine. In its +simplest, form there are only two states: `LOCKED` and `UNLOCKED`. Two +events, `COIN` and `PUSH` can happen, depending on whether someone +makes a payment or tries to go through the turnstile. +The following image shows the state machine: image::images/statechart1.png[width=500] +The following listing shows the enumeration that defines the possible states: + +==== .States [source,java,indent=0] ---- include::samples/demo/turnstile/Application.java[tags=snippetB] ---- +==== +The following listing shows the enumeration that defines the events: + +==== .Events [source,java,indent=0] ---- include::samples/demo/turnstile/Application.java[tags=snippetC] ---- +==== +The following listing shows the code that configures the state machine: + +==== .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. +You can see how this sample state machine interacts with events by +running the `turnstile` sample. The following listing shows how to do so +and shows the command's output: + +==== [source,text,subs="verbatim,attributes"] ---- $ java -jar spring-statemachine-samples-turnstile-{revnumber}.jar @@ -130,59 +146,91 @@ sm>sm event PUSH State changed to LOCKED Event PUSH send ---- +==== [[statemachine-examples-showcase]] == Showcase -Showcase is a complex state machine showing all possible transition + +Showcase is a complex state machine that shows all possible transition topologies up to four levels of state nesting. +The following image shows the state machine: image::images/statechart2.png[width=500] +The following listing shows the enumeration that defines the possible states: + +==== .States [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetB] ---- +==== +The following listing shows the enumeration that defines the events: + +==== .Events [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetC] ---- +==== +The following listing shows the code that configures the state machine: + +==== .Configuration - states [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetAA] ---- +==== +The following listing shows the code that configures the state machine's transitions: + +==== .Configuration - transitions [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetAB] ---- +==== -.Configuration - actions and guard +The following listing shows the code that configures the state machine's actions and guards: + +==== +.Configuration - actions and guards [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetAC] ---- +==== +The following listing shows how the single action is defined: + +==== .Action [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetD] ---- +==== +The following listing shows how the single guard is defined: + +==== .Guard [source,java,indent=0] ---- include::samples/demo/showcase/Application.java[tags=snippetE] ---- +==== -Let's go through what this state machine do when it's executed and we -send various event to it. +The following listing shows the output that this state machine produces when it runs and +various events are sent to it: +==== [source,text] ---- sm>sm start @@ -223,25 +271,27 @@ Entry state S1 Entry state S11 Event A send ---- +==== -What happens in above sample: +In the preceding output, we can see that: -* State machine is started which takes it to its initial state _S11_ - via superstates _S1_ and _S0_. Also extended state variable `foo` is - init to `0`. -* We try to execute self transition in state _S1_ with event _A_ but - nothing happens because transition is guarded by variable `foo` to +* The state machine is started, which takes it to its initial state (`S11`) + through superstates (`S1`) and (`S0`). Also, the extended state variable, `foo`, is + initialized to `0`. +* We try to execute a self transition in state `S1` with event `A`, but + nothing happens because the transition is guarded by variable `foo` to be `1`. -* We send event _C_ which takes us to other state machine where - initial state _S211_ and its superstates are entered. In there we - can use event _H_ which does a simple internal transition to flip - variable `foo`. Then we simply go back using event _C_. -* Event _A_ is sent again and now _S1_ does a self transition because - guard evaluates true. +* We send event `C`, which takes us to the other state machine, where + the initial state (`S211`) and its superstates are entered. In there, we + can use event `H`, which does a simple internal transition to flip the + `foo` variable. Then we go back by using event `C`. +* Event `A` is sent again, and now `S1` does a self transition because the + guard evaluates to `true`. -Let's take closer look of how hierarchical states and their event -handling works with a below example. +The following example offers a closer look at how hierarchical states and their event +handling works: +==== [source,text] ---- sm>sm variables @@ -291,50 +341,55 @@ Event H send sm>sm variables foo=0 ---- +==== -What happens in above sample: +In the preceding sample: * We print extended state variables in various stages. -* With event _H_ we end up executing internal transition - which is logged with source state. -* It's also worth to pay attention to how event _H_ is handled in - different states _S0_, _S1_ and _S2_. This is a good example of how - hierarchical states and their event handling works. If state _S2_ is - unable to handle event _H_ due to guard condition, its parent is - checked next. This guarantees that while on state _S2_, `foo` flag - is always flipped around. However in state _S1_ event _H_ always - match to its dummy transition without guard or action, not never +* With event `H`, we end up running an internal transition, + which is logged with its source state. +* Note how event `H` is handled in + different states (`S0`, `S1`, and `S2`). This is a good example of how + hierarchical states and their event handling works. If state `S2` is + unable to handle event `H` due to a guard condition, its parent is + checked next. This guarantees that, while the machine is on state `S2`, the `foo` flag + is always flipped around. However, in state `S1`, event `H` always + matches to its dummy transition without guard or action, so it never happens. [[statemachine-examples-cdplayer]] == CD Player -CD Player is a sample which resembles better use case of most of use have -used in a real world. CD Player itself is a really simple entity where -user can open a deck, insert or change a disk, then drive player -functionality by pressing various buttons like _eject_, _play_, -_stop_, _pause_, _rewind_ and _backward_. -How many of us have really given a thought of what it will take to -make a code for a CD Player which interacts with a hardware. Yes, -concept of a player is overly simple but if you look behind the scenes +CD Player is a sample which resembles a use case that many people have +used in the real world. CD Player itself is a really simple entity that allows a +user to open a deck, insert or change a disk, and then drive the player's +functionality by pressing various buttons (`eject`, `play`, +`stop`, `pause`, `rewind`, and `backward`). + +How many of us have really given thought to what it will take to +make code that interacts with hardware to drive a CD Player. Yes, the +concept of a player is simple, but, if you look behind the scenes, things actually get a bit convoluted. -You've probably noticed that if your deck is open and you press play, -deck will close and a song will start to play if CD was inserted in -a first place. In a sense when deck is open you first need to close -it and then try to start playing if cd is actually inserted. Hopefully -you have now realised that a simple CD Player is not anymore so simple. -Sure you can wrap all this with a simple class with few boolean variables -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. +You have probably noticed that, if your deck is open and you press play, the +deck closes and a song starts to play (if a CD was inserted). +In a sense, when the deck is open, you first need to close +it and then try to start playing (again, if a CD is actually inserted). Hopefully, +you have now realized that a simple CD Player is so simple. +Sure, you can wrap all this with a simple class that has a few boolean variables +and probably a few nested if-else clauses. That will do the job, but what +about if you need to make all this behavior much more complex? Do you +really want to keep adding more flags and if-else clauses? + +The following image shows the state machine for our simple CD player: image::images/statechart3.png[width=500] -Let's go through how this sample and its state machine is designed and -how those two interacts with each other. Below three config sections -are used withing a _EnumStateMachineConfigurerAdapter_. +The rest of this section goes through how this sample and its state machine is designed and +how those two interacts with each other. The following three configuration sections +are used within an `EnumStateMachineConfigurerAdapter`. +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetAA] @@ -349,141 +404,190 @@ include::samples/demo/cdplayer/Application.java[tags=snippetAB] ---- include::samples/demo/cdplayer/Application.java[tags=snippetAC] ---- +==== -What we did in above configuration: +In the preceding configuration: -* We used EnumStateMachineConfigurerAdapter to configure states and +* We used `EnumStateMachineConfigurerAdapter` to configure states and transitions. -* States _CLOSED_ and _OPEN_ are defined as substates of _IDLE_, - states _PLAYING_ and _PAUSED_ are defined as substates of _BUSY_. -* With state _CLOSED_ we added entry action as bean - _closedEntryAction_. -* With transition we mostly mapped events to expected state - transitions like _EJECT_ closing and opening a deck, _PLAY_, _STOP_ - and _PAUSE_ doing their natural transitions. Few words to mention - what we did for other transitions. -** With source state _PLAYING_ we added a timer trigger which is +* The `CLOSED` and `OPEN` states are defined as substates of `IDLE`, and + the `PLAYING` and `PAUSED` states are defined as substates of `BUSY`. +* With the `CLOSED` state, we added an entry action as a bean called + `closedEntryAction`. +* In the transitions we mostly map events to expected state + transitions, such as `EJECT` closing and opening a deck and `PLAY`, `STOP`, + and `PAUSE` doing their natural transitions. For other transitions, we did the following: +** For source state `PLAYING`, we added a timer trigger, which is 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_. -** 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. +to have a facility for making the decision about when to switch the to next track. +** For the `PLAY` event, if the source state is `IDLE` and the target state is + `BUSY`, we defined an action called `playAction` and a guard called `playGuard`. +** For the `LOAD` event and the `OPEN` state, we defined an internal +transition with an action called `loadAction`, which tracks inserting a disc with +extended-state variables. +** The `PLAYING` state defines three internal transitions. One is +triggered by a timer that runs an action called `playingAction`, which updates +the extended state variables. The other two transitions use `trackAction` +with different events (`BACK` and `FORWARD`, respectively) to handle +when the user wants to go back or forward in tracks. -This machine only have six states which are introduced as an enum. +This machine has only have six states, which are defined by the following enumeration: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetB] ---- +==== -Events represent, in a sense in this example, what buttons user would -press and if user loads a cd disc into a deck. +Events represent the buttons the user can +press and whether the user loads a disc into the player. +The following enumeration defines the events: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetC] ---- +==== -Beans _cdPlayer_ and _library_ are just used with a sample to drive -the application. +The `cdPlayer` and `library` beans are used to drive the application. +The following listing shows the definition of these two beans: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetD] ---- +==== -We can define extended state variable key as simple enums. + +We define extended state variable keys as simple enumerations, +as the following listing shows: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetE] ---- +==== -We wanted to make this samply type safe so we're defining our own -annotation _@StatesOnTransition_ which have a mandatory meta -annotation _@OnTransition_. +We wanted to make this sample type safe, so we define our own +annotation (`@StatesOnTransition`), which has a mandatory meta +annotation (`@OnTransition`). +The following listing defines the `@StatesOnTransition` annotation: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetF] ---- +==== -_ClosedEntryAction_ is a entry action for state _CLOSED_ to simply -send and _PLAY_ event to a statemachine if cd disc is present. +`ClosedEntryAction` is an entry action for the `CLOSED` state, to +send a `PLAY` event to the state machine if a disc is present. +The following listing defines `ClosedEntryAction`: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetG] ---- +==== -_LoadAction_ is simply updating extended state variable if event -headers contained information about a cd disc to load. +`LoadAction` update an extended state variable if event +headers contain information about a disc to load. +The following listing defines `LoadAction`: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetH] ---- +==== -_PlayAction_ is simply resetting player elapsed time which is kept as +`PlayAction` resets the player's elapsed time, which is kept as an extended state variable. +The following listing defines `PlayAction`: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetI] ---- +==== -_PlayGuard_ is used to guard transition from _IDLE_ to _BUSY_ with -event _PLAY_ if extended state variable _CD_ doesn't indicate that cd +`PlayGuard` guards the transition from `IDLE` to `BUSY` with the +`PLAY` event if the `CD` extended state variable does not indicate that a disc has been loaded. +The following listing defines `PlayGuard`: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetJ] ---- +==== -_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. +`PlayingAction` updates an extended state variable called `ELAPSEDTIME`, which +the player can use to read and update its LCD status display. `PlayingAction` also handles +track shifting when the user goe back or forward in tracks. +The following example defines `PlayingAction`: + +==== [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. +`TrackAction` handles track shift actions when the user goes back or forward +in tracks. If a track is the last on a disc, playing is stopped and the `STOP` +event is sent to a state machine. +The following example defines `TrackAction`: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/Application.java[tags=snippetL] ---- +==== -One other important aspect of a state machines is that they have their -own responsibilities mostly around handling states and all application -level logic should be kept outside. This means that application needs -to have a ways to interact with a state machine and below sample is -how cdplayer does it order to update lcd status. Also pay attention -that we annotated _CdPlayer_ with _@WithStateMachine_ which instructs -state machine to find methods from your pojo which are then called +One other important aspect of state machines is that they have their +own responsibilities (mostly around handling states) and that all application +level logic should be kept outside. This means that applications need +to have a ways to interact with a state machine. Also, note +that we annotated `CdPlayer` with `@WithStateMachine`, which instructs a +state machine to find methods from your POJO, which are then called with various transitions. +The following example shows how it updates its LCD status display: +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/CdPlayer.java[tags=snippetA] ---- +==== -In above example we use _@OnTransition_ annotation to hook a callback -when transition happens with a target state _BUSY_. +In the preceding example, we use the `@OnTransition` annotation to hook a callback +when a transition happens with a target state of `BUSY`. +The following listing shows how our state machine handles whether the player is closed: + +==== [source,java,indent=0] ---- include::samples/demo/cdplayer/CdPlayer.java[tags=snippetB] ---- +==== -_@OnTransition_ we used above can only be used with strings which are -matched from enums. _@StatesOnTransition_ is then something what user -can create into his own application to get a type safe annotation where -a real enums can be used. +`@OnTransition` (which we used in the preceding examples) can only be +used with strings that are matched from enumerations. `@StatesOnTransition` +lets you create your own type-safe annotations that use real enumerations. -Let's see an example how this state machine actually works. +The following example shows how this state machine actually works. +==== [source,text] ---- sm>sm start @@ -534,98 +638,124 @@ Entry state CLOSED sm>cd lcd Greatest Hits ---- +==== -What happened in above run: +In the preceding run: -* State machine is started which causes machine to get initialized. -* CD Player lcd screen status is printed. -* CD Library is printed. -* CD Player deck is opened. -* CD with index 0 is loaded into a deck. -* Play is causing deck to get closed and immediate playing because cd +* The state machine is started, which causes the machine to be initialized. +* The CD player's LCD screen status is printed. +* The CD library is printed. +* The CD player's deck is opened. +* The CD with index 0 is loaded into a deck. +* Play causes the deck to get closed and immediate play, because a disc was inserted. -* We print lcd status and request next track. +* We print the LCD status and request the next track. * We stop playing. [[statemachine-examples-tasks]] == Tasks -Tasks is a sample demonstrating a parallel task handling within a -regions and additionally adds an error handling to either -automatically or manually fixing task problems before continuing back -to a state where tasks can be run again. +The Tasks sample demonstrates parallel task handling within +regions and adds error handling to either +automatically or manually fix task problems before continuing back +to a state where the tasks can be run again. +The following image shows the Tasks state machine: image::images/statechart5.png[width=500] -On a high level what happens in this state machine is: +On a high level, in this state machine: -* We're always trying to get into READY state so that we can use event - RUN to execute tasks. -* TASKS state which is composed with 3 independent regions has been - put in a middle of FORK and JOIN states which will cause regions to - go into its initial states and to be joined by end states. -* From JOIN state we go automatically into a CHOICE state which checks - existence of error flags in extended state variables. Tasks can set - these flags and it gives CHOICE state a possibility to go into ERROR - state where errors can be handled either automatically or manually. -* AUTOMATIC state in ERROR can try to automatically fix error and goes - back to READY if it succeed to do so. If error is something what - can't be handled automatically, user intervention is needed and - machine is put into MANUAL state via FALLBACK event. +* We always try to get into the `READY` state so that we can use the + RUN event to execute tasks. +* Tkhe `TASKS` state, which is composed of three independent regions, has been + put in the middle of `FORK` and `JOIN` states, which will cause the regions to + go into their initial states and to be joined by their end states. +* From the `JOIN` state, we automatically go into a `CHOICE` state, which checks + for the existence of error flags in extended state variables. Tasks can set + these flags, and doing so gives the `CHOICE` state the ability to go into the `ERROR` + state, where errors can be handled either automatically or manually. +* The `AUTOMATIC` state in `ERROR` can try to automatically fix an error and goes + back to `READY` if it succeeds. If the error is something what + cannot be handled automatically, user intervention is needed and the + machine is put into the `MANUAL` state by the `FALLBACK` event. +The following listing shows the enumeration that defines the possible states: + +==== .States [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetB] ---- +==== +The following listing shows the enumeration that defines the events: + +==== .Events [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetC] ---- +==== +The following listing configures the possible states: + +==== .Configuration - states [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetAA] ---- +==== +The following listing configures the possible transitions: + +==== .Configuration - transitions [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetAB] ---- +==== -Guard below is guarding choice entry into a ERROR state and needs to -return TRUE if error has happened. For this guard simply checks that -all extended state variables(T1, T2 and T3) are TRUE. +The following guard sends a choice entry into the `ERROR` state and needs to +return `TRUE` if an error has happened. This guard checks that +all extended state variables(`T1`, `T2`, and `T3`) are `TRUE`. +==== [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetAC] ---- +==== -Actions below will simply send event to a state machine to request -next step which would be either fallback or continue back to ready. +The following actions below send events to the state machine to request +the next step, which is either to fall back or to continue back to ready. +==== [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetAD] ---- +==== -Currently default region execution is synchronous but it can be -changed to asynchronous by changing `TaskExecutor`. Task will simulate -work by sleeping 2 seconds so you'll able to see how actions in -regions are executed parallel. +Currently, the default region execution is synchronous, but you can change +it to be asynchronous by changing `TaskExecutor`. Task simulates +work by sleeping two seconds so that you can see how actions in +regions are executed inparallel. +The following listing shows the `TaskExecutor` bean definition: +==== [source,java,indent=0] ---- include::samples/demo/tasks/Application.java[tags=snippetAE] ---- +==== -Let's see an examples how this state machine actually works. +The following example shows how this state machine actually works: +==== [source,text] ---- sm>sm start @@ -651,9 +781,12 @@ Entry state JOIN Exit state JOIN Entry state READY ---- +==== -In above we can execute tasks multiple times. +In the preceding listing, we can see that tasks run multiple times. +In the next listing, we introduce errors: +==== [source,text] ---- sm>tasks list @@ -687,10 +820,13 @@ Exit state AUTOMATIC Exit state ERROR Entry state READY ---- +==== -In above, if we simulate failure for task T1, it is fixed +In the preceding listing, if we simulate a failure for task T1, it is fixed automatically. +In the next listing, we introduce more errors: +==== [source,text] ---- sm>tasks list @@ -725,49 +861,69 @@ Exit state MANUAL Exit state ERROR Entry state READY ---- +==== -In above if we simulate failure for either task T2 or T3, state -machine goes to MANUAL state where problem needs to be fixed manually -before we're able to go back to READY state. +In the precding example, if we simulate failure for either task `T2` or `T3`, the state +machine goes to the `MANUAL` state, where problem needs to be fixed manually +before it can go back to the `READY` state. [[statemachine-examples-washer]] == Washer -Washer is a sample demonstrating a use of a history state to recover a -running state configuration with a simulated power off situation. +The washer sample demonstrates how to use a history state to recover a +running state configuration with a simulated power-off situation. -Anyone ever used a washing machine knows that if you can somehow pause -the program it will continue from a same state when lid is closed. -This kind of behaviour can be implemented in a state machine by using +Anyone who has ever used a washing machine knows that if you somehow pause +the program, it continue from the same state when unpaused. +You can implement this kind of behavior in a state machine by using a history pseudo state. +The following image shows our state machine for a washer: image::images/statechart6.png[width=500] +The following listing shows the enumeration that defines the possible states: + +==== .States [source,java,indent=0] ---- include::samples/demo/washer/Application.java[tags=snippetB] ---- +==== +The following listing shows the enumeration that defines the events: + +==== .Events [source,java,indent=0] ---- include::samples/demo/washer/Application.java[tags=snippetC] ---- +==== +The following listing configures the possible states: + +==== .Configuration - states [source,java,indent=0] ---- include::samples/demo/washer/Application.java[tags=snippetAA] ---- +==== +The following listing configures the possible transitions: + +==== .Configuration - transitions [source,java,indent=0] ---- include::samples/demo/washer/Application.java[tags=snippetAB] ---- +==== -Let's see an example how this state machine actually works. +The following example shows how this state machine actually works: + +==== [source,text] ---- sm>sm start @@ -798,50 +954,61 @@ Entry state WASHING Entry state DRYING Event RESTOREPOWER send ---- +==== -What happened in above run: +In the preceding run: -* State machine is started which causes machine to get initialized. -* We go to RINSING state. -* We go to DRYING state. -* We cut power and go to POWEROFF state. -* State is restored via HISTORY state which takes state machine back +* The state machine is started, which causes machine to get initialized. +* The state machine goes to RINSING state. +* The state machine goes to DRYING state. +* The state machine cuts power and goes to POWEROFF state. +* The state is restored from the HISTORY state, which takes state machine back to its previous known state. [[statemachine-examples-persist]] == Persist -Persist is a sample using recipe <> to -demonstrate how a database entry update logic can be controlled by a + +Persist is a sample that uses the <> recipe to +demonstrate how database entry update logic can be controlled by a state machine. -The state machine logic and configuration is shown above: +The following image shows the state machine logic and configuration: image::images/statechart10.png[width=500] +The following listing shows the state machine configuration: + +==== .StateMachine Config [source,java,indent=0] ---- include::samples/demo/persist/Application.java[tags=snippetA] ---- +==== -`PersistStateMachineHandler` can be created using a below config: +The following configuration creates `PersistStateMachineHandler`: +==== .Handler Config [source,java,indent=0] ---- include::samples/demo/persist/Application.java[tags=snippetB] ---- +==== -Order class used with this sample is shown below: +The following listing shows the `Order` class used with this sample: +==== .Order Class [source,java,indent=0] ---- include::samples/demo/persist/Application.java[tags=snippetC] ---- +==== -Now let's see how this example works. +The following example shows the state machine's output: +==== [source,text] ---- sm>persist db @@ -870,87 +1037,102 @@ Order [id=4, state=DELIVERED] Order [id=1, state=PROCESSING] Order [id=3, state=DELIVERED] ---- +==== -What happened in above run: +In the preceding run, the state machine: -* We listed rows from an existing embedded database which is already - populated with sample data. -* We request to update order `1` into `PROCESSING` state. -* We list db entries again and see that state has been changed from - `PLACED` into a `PROCESSING`. -* We do update for order `3` to update state from `SENT` into +* Listed rows from an existing embedded database, which is already + populated with sample data. +* Requested to update order `1` into the `PROCESSING` state. +* List database entries again and see that the state has been changed from + `PLACED` to `PROCESSING`. +* Update order `3` to update its state from `SENT` to `DELIVERED`. [NOTE] ==== -If you're wondering where is the database because there are literally no -signs of it in a sample code. Sample is based on Spring Boot and -because necessary classes are in a classpath, embedded `HSQL` instance +You may wonder where the database is, because there are literally no +signs of it in the sample code. The sample is based on Spring Boot and, +because the necessary classes are in a classpath, an embedded `HSQL` instance is created automatically. -Spring Boot will even create an instance of `JdbcTemplate` which you -can just autowire like how it's done in `Persist.java`. +Spring Boot even creates an instance of `JdbcTemplate`, which you +can autowire, as we did in `Persist.java`, shown in the following listing: +===== [source,java,indent=0] ---- include::samples/demo/persist/Persist.java[tags=snippetA] ---- +===== ==== -Finally we need to handle state changes: +Next, we need to handle state changes. The following listing shows how we do so: +==== [source,java,indent=0] ---- include::samples/demo/persist/Persist.java[tags=snippetB] ---- +==== -And use a `PersistStateChangeListener` to update database: +Finally, we use a `PersistStateChangeListener` to update the database, as the +following listing shows: +==== [source,java,indent=0] ---- include::samples/demo/persist/Persist.java[tags=snippetC] ---- +==== [[statemachine-examples-zookeeper]] == Zookeeper -Zookeeper is a distributed version from sample -<>. -[NOTE] +Zookeeper is a distributed version from the +<> sample. + +NOTE: This sample needs an external `Zookeeper` instance that is accessible from +`localhost` and has the default port and settings. + +Configuration of this sample is almost the same as the `turnstile` sample. We +add only the configuration for the distributed state machine where we +configure `StateMachineEnsemble`, as the following listing shows: + ==== -This sample needs and external `Zookeeper` instance accessible from -`localhost` with default port and settings. -==== - -Configuration of this sample is almost same as `turnstile` sample. We -only add configuration for distributed state machine where we -configure `StateMachineEnsemble`. - [source,java,indent=0] ---- include::samples/demo/zookeeper/Application.java[tags=snippetA] ---- +==== -Actual `StateMachineEnsemble` needs to be created as bean together -with `CuratorFramework` client. +The actual `StateMachineEnsemble` needs to be created as a bean, together +with the `CuratorFramework` client, as the following example shows: +==== [source,java,indent=0] ---- include::samples/demo/zookeeper/Application.java[tags=snippetB] ---- +==== -Let's go through a simple example where two different shell instances are -started with command +For the next example, we need to create two different shell instances. +We need to create one instance, see what happens, and then create the second instance. +The following command starts the shell instances (remember to start only one instance for now): +==== [source,text,subs="attributes"] ---- @n1:~# java -jar spring-statemachine-samples-zookeeper-{revnumber}.jar ---- +==== -First open first shell instance(do not start second instance yet). -When state machine is started it will end up into its initial state -`LOCKED`. Then send event `COIN` to transit into `UNLOCKED` state. +When state machine is started, its initial state is +`LOCKED`. Then it sends a `COIN` event to transition into `UNLOCKED` state. +The following example shows what happens: + +==== .Shell1 [source,text] ---- @@ -966,11 +1148,16 @@ Event COIN send sm>sm state UNLOCKED ---- +==== -Open second shell instance and start a state machine. You should see -that distributed state `UNLOCKED` is entered instead of default -initial state `LOCKED`. +Now you can open a second shell instance and start a state machine, +by using the same command that you used to start the first state machine. You should see +that the distributed state (`UNLOCKED`) is entered instead of the default +initial state (`LOCKED`). +The following example shows the state machine and its output: + +==== .Shell2 [source,text] ---- @@ -980,10 +1167,13 @@ State machine started sm>sm state UNLOCKED ---- +==== -Then from either of a shells(we use second instance here) send event -`PUSH` to transit from `UNLOCKED` into `LOCKED` state. +Then from either shell (we use second instance in the next example), send a +`PUSH` event to transit from the `UNLOCKED` into the `LOCKED` state. +The following example shows the state machine command and its output: +==== .Shell2 [source,text] ---- @@ -992,730 +1182,884 @@ Exit state UNLOCKED Entry state LOCKED Event PUSH send ---- +==== -In other shell you should see state getting changed automatically +In the other shell (the first shell if you ran the preceding command in the second shell), +you should see the state be changed automatically, based on distributed state kept in Zookeeper. +The following example shows the state machine command and its output: +==== .Shell1 [source,text] ---- sm>Exit state UNLOCKED Entry state LOCKED ---- +==== [[statemachine-examples-web]] == Web -Web is a distributed state machine example using a zookeeper to handle -distributed state. This example is meant to be run on a multiple -browser sessions against a multiple different hosts. -This sample is using a modified state machine structure from a +Web is a distributed state machine example that uses a zookeeper state machine to handle +distributed state. See <>. + +NOTE: This example is meant to be run on multiple +browser sessions against multiple different hosts. + +This sample uses a modified state machine structure from <> to work with a distributed state -machine. The state machine logic is shown above: +machine. The following image shows the state machine logic: image::images/statechart11.png[width=500] -[NOTE] -==== -Due to nature of this sample an instance of a `Zookeeper` is expected to +NOTE: Due to the nature of this sample, an instance of a `Zookeeper` state machine is expected to be available from a localhost for every individual sample instance. -==== -Let's go through a simple example where three different sample instances are -started. If you are running different instances on a same host you need to -distinguish used port by adding `--server.port=` to the command. -Otherwise default port for each host will be `8080`. +This demonstration uses an example that starts three different sample instances. +If you run different instances on the same host, you need to +distinguish the port each one uses by adding `--server.port=` to the command. +Otherwise the default port for each host is `8080`. -In this sample run we have three hosts, `n1`, `n2` and `n3` which all -have a local zookeeper instance running and a state machine sample running +In this sample run, we have three hosts: `n1`, `n2`, and `n3`. Each one +has a local zookeeper instance running and a state machine sample running on a port `8080`. +In there different terminals, start the three different state machines by running +the following command: + +==== [source,text,subs="attributes"] ---- -@n1:~# java -jar spring-statemachine-samples-web-{revnumber}.jar -@n2:~# java -jar spring-statemachine-samples-web-{revnumber}.jar -@n3:~# java -jar spring-statemachine-samples-web-{revnumber}.jar +# java -jar spring-statemachine-samples-web-{revnumber}.jar ---- +==== -When all instances are running you should see all showing similar -information via a browser where states are `S0`, `S1` and `S11`, -and extended state variable `foo=0`. Main state is `S11`. +When all instances are running, you should see that all show similar +information when you access them with a browser. The states should be `S0`, `S1`, and `S11`. +The extended state variable named `foo` should have a value of `0`. The main state is `S11`. image::images/sm-dist-n1-1.png[width=500] -When you press button `Event C` in any of a browser window, -distributed state is changed to `S211` which is the target state -denoted by transition associated with an event `C`. +When you press the `Event C` button in any of the browser windows, the +distributed state is changed to `S211,` which is the target state +denoted by the transition associated with an event of type `C`. +The following image shows the change: image::images/sm-dist-n2-2.png[width=500] -Then let's press button `Event H` and what is supposed to happen is -that internal transition is executed on all state machines changing -extended state variable `foo` from value `0` to `1`. This change is -first done on a state machine receiving the event and then propagated -to other state machines. You should only see variable `foo` to change +Now we can press the `Event H` button and see that the +internal transition runs on all state machines to change the +the value of the extended state variable named `foo` from `0` to `1`. This change is +first done on the state machine that receives the event and is then propagated +to the other state machines. You should see only the variable named `foo` change from `0` to `1`. image::images/sm-dist-n3-3.png[width=500] -Last we simply send an event `Event K` which is supposed to take state -machine state back to state `S11` and you should see this happening in -all browser sessions. +Finally, we can send `Event K`, which takes the state +machine state back to state `S11`. You should see this happen in +all of the browsers. The following image shows the result in one browser: image::images/sm-dist-n1-4.png[width=500] [[statemachine-examples-scope]] + == Scope -Scope is a state machine example using a session scope to provide + +Scope is a state machine example that uses session scope to provide an individual instance for every user. +The following image shows the states and events within the Scope state machine: image::images/statechart12.png[width=500] -This is a simple state machine having states `S0`, `S1` and `S2`. -Transitions between those are controlled via events `A`, `B` and `C` -as shown in a state chart. +This simple state machine has three states: `S0`, `S1`, and `S2`. +Transitions between those are controlled by three events: `A`, `B`, and `C`. +To start the state machine, run the following command in a terminal: + +==== [source,text,subs="attributes"] ---- -@n1:~# java -jar spring-statemachine-samples-scope-{revnumber}.jar +# java -jar spring-statemachine-samples-scope-{revnumber}.jar ---- +==== -When instance is running you can open a browser and play with a state -machine. If you open same page using a different browser, i.e one in -_Chrome_ and one in _Firefox_, you should get a new state machine -instance per user session. +When the instance is running, you can open a browser and play with the state +machine. If you open the same page in a different browser, (for example, one in +Chrome and one in Firefox), you should get a new state machine +instance for each user session. +The following image shows the state machine in a browser: image::images/sm-scope-1.png[width=500] [[statemachine-examples-security]] == Security -Security is a state machine example using most of a combinations of -securing a state machine. It is securing sending events, transitions + +Security is a state machine example that uses most of the possible combinations of +securing a state machine. It secures sending events, transitions, and actions. +The following image shows the state machine's states and events: image::images/statechart13.png[width=500] +To start the state machine, run the following command: + +==== [source,text,subs="attributes"] ---- -@n1:~# java -jar spring-statemachine-samples-secure-{revnumber}.jar +# java -jar spring-statemachine-samples-secure-{revnumber}.jar ---- +==== -We secure event sending with a users having a role `USER`. None of -a other users imposed by a _Spring Security_ can't send events into a +We secure event sending by requiring that users have a role of `USER`. +Spring Security ensures that no other users can send events to this state machine. +The following listing secures event sending: +==== [source,java,indent=0] ---- include::samples/demo/security/StateMachineConfig.java[tags=snippetA] ---- +==== -In this sample we define two users, _user_ having a role `USER` and -_admin_ having both roles `USER` and `ADMIN`. Authentication for both -user for password is `password`. +In this sample we define two users: +* A user named `user` who has a role of `USER` +* A user named `admin` who has two roles: `USER` and `ADMIN` + +The password for both users is `password`. +The following listing configures the two users: + +==== [source,java,indent=0] ---- include::samples/demo/security/StateMachineConfig.java[tags=snippetE] ---- +==== -We define various transitions between states according to a statechart -seen above. Only a user with active `ADMIN` role can execute -external transitions between `S2` and `S3`. Similarly `ADMIN` can only -execute internal transition in a state `S1`. +We define various transitions between states according to the state chart +shown at the beginning of the example. Only a user with an active `ADMIN` role can run +the external transitions between `S2` and `S3`. Similarly only an `ADMIN` can +run the internal transition the `S1` state. +The following listing defines the transitions, including their security: +==== [source,java,indent=0] ---- include::samples/demo/security/StateMachineConfig.java[tags=snippetB] ---- +==== -`Action` `adminAction` is secured with a role `ADMIN`. +The following listing uses a method called `adminAction` whose return type is `Action` to +specify that the action is secured with a role of `ADMIN`: +==== [source,java,indent=0] ---- include::samples/demo/security/StateMachineConfig.java[tags=snippetC] ---- +==== -Below `Action` would only be executed with internal transition in a -state `S1` when event `F` is send. Transition itself is secured with a -role `ADMIN` so this transition will not be executed if current user -does not hate that role. +The following `Action` runs an internal transition in state `S` when event `F` is sent. +==== [source,java,indent=0] ---- include::samples/demo/security/StateMachineConfig.java[tags=snippetD] ---- +==== + +The transition itself is secured with a +role of `ADMIN`, so this transition does not run if the current user +does not hate that role. [[statemachine-examples-eventservice]] == Event Service -Event Service is an example how state machine concepts can be used as -a processing engine for events. Sample was born out from a question: -[NOTE] -==== -Can Spring Statemachine be used as a microservice to feed events to it -with millions different state machine instances. -==== +The event service example shows how you can use state machine concepts as +a processing engine for events. This sample evolved from a question: -In this example we will use a `Redis` to persist a state machine +Can I use Spring Statemachine as a microservice to feed events to +different state machine instances? In fact, Spring Statemachine can feed +events to potentially millions of different state machine instances. + +This example uses a `Redis` instance to persist state machine instances. -Obviously a million state machine instances in a jvm would be -a relatively bad idea due to memory constraints. This simply leads to -other available features from a Spring Statemachine to persist a +Obviously, a million state machine instances in a JVM would be +a bad idea, due to memory constraints. This leads to +other features of Spring Statemachine that let you persist a `StateMachineContext` and re-use existing instances. -We assume few things like there is a shopping application which is -sending different types of `PageView` events into a separate -microservice which is then tracking user behaviour using a state -machine. State model is shown below which simply have few states -representing user navigating on product items list, add and remove -items from a cart and going to a payment page and initiating a pay -operation. Actual shopping application would send these events into -this service for example using a simple rest calls. More about this -later. - -[NOTE] -==== -Remember that focus here is to have an application which is exposing a -`REST` api user can use to send events which would be processed by a -state machine per request. -==== +For this example, we assume that a shopping application +sends different types of `PageView` events to a separate +microservice which then tracks user behavior by using a state +machine. The following image shows the state model, which has a few states +that represent a user navigating a product items list, adding and removing +items from a cart, going to a payment page, and initiating a payment +operation: image::images/statechart14.png[width=500] -In below state machine configuration we simply model what we have in a -state chart. Various actions are updating state machine `Extended -State` to track number of entries into various states and also how -many times internal transition for `ADD` and `DEL` are called and if -`PAY` has been executed. Don't focus on `stateMachineTarget` or -`@Scope` for now, as we'll explain those in a bit. +An actual shopping application would send these events into +this service by (for example) using a rest call. More about this +later. +NOTE: Remember that the focus here is to have an application that exposes a +`REST` API that the user can use to send events that can be processed by a +state machine for each request. + +The following state machine configuration models what we have in a +state chart. Various actions update the state machine's `Extended +State` to track the number of entries into various states and also how +many times the internal transitions for `ADD` and `DEL` are called and whether +`PAY` has been executed: + +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetC] ---- +==== -In below config we set up a `RedisConnectionFactory` which defaults to +Do not focus on `stateMachineTarget` or +`@Scope` for now, as we explain those later in this section. + +We set up a `RedisConnectionFactory` that defaults to localhost and default port. We use `StateMachinePersist` with a -`RepositoryStateMachinePersist` implementation. Finally we create a -`RedisStateMachinePersister` which underneath uses a previously +`RepositoryStateMachinePersist` implementation. Finally, we create a +`RedisStateMachinePersister` that uses a previously created `StateMachinePersist` bean. -These are then used in a `Controller` handling `REST` calls. +These are then used in a `Controller` that handles `REST` calls, +as the following listing shows: +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetD] ---- +==== -We now get into why `StateMachine` was created as `stateMachineTarget` -and a `prototype` bean. State machine instantiation is a relatively -expensive operation so it is better to try to pool instances instead -of instantiating a new instance with every request. For this we first -create a `poolTargetSource` which wraps `stateMachineTarget` and pools -it with max size of 3. This `poolTargetSource` is then proxied with -`ProxyFactoryBean` using a `request` scope. Effectively this means -that every `REST` request will get pooled state machine instance from -a bean factory. It's shown later how these are used. +We create a bean named `stateMachineTarget`. +State machine instantiation is a relatively +expensive operation, so it is better to try to pool instances instead +of instantiating a new instance for every request. To do so, we first +create a `poolTargetSource` that wraps `stateMachineTarget` and pools +it with a max size of three. When then proxy this `poolTargetSource` with +`ProxyFactoryBean` by using a `request` scope. Effectively, this means +that every `REST` request gets a pooled state machine instance from +a bean factory. Later, we show how these instances are used. +The following listing shows how we create the `ProxyFactoryBean` +and set the target source: +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetA] ---- +==== +The following listing shows we set the maximum size and set the target bean name: + +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetB] ---- +==== -Let's get into actual demo. You need to have a redis running on a -localhost with a default settings. Then run the boot based sample -application: +Now we can get into actual demo. You need to have a Redis server running on +localhost with default settings. Then you need to run the Boot-based sample +application by running the following command: +==== [source,text,subs="attributes"] ---- -@n1:~# java -jar spring-statemachine-samples-eventservice-{revnumber}.jar +# java -jar spring-statemachine-samples-eventservice-{revnumber}.jar ---- +==== -In a browser you see something like: +In a browser, you see something like the following: image::images/sm-eventservice-1.png[width=500] -In this UI you have three users you can use, `joe`, `bob` and `dave`. -Clicking button will show current state and extended state. Enabling a -radio button before clicking users will send particular event for that -user. This is a way you can play with this using an UI. +In this UI, you can use three users: `joe`, `bob`, and `dave`. +Clicking a button shows the current state and the extended state. Enabling a +radio button before clicking a button sends a particular event for that +user. This arrangement lets you play with the UI. -In our `StateMachineController` we autowire `StateMachine` and -`StateMachinePersist`. `StateMachine` is a `request` scoped so you'll -get new instance per request while `StateMachinePersist` is normal +In our `StateMachineController`, we autowire `StateMachine` and +`StateMachinePersister`. `StateMachine` is `request` scoped, so you +get a new instance for each request, while `StateMachinePersist` is a normal singleton bean. +The following listing autowires `StateMachine` and +`StateMachinePersist`: +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineController.java[tags=snippetA] ---- +==== -Below `feedAndGetState` is just used with an UI to do same things what -actual `REST` api will do. +In the following listing, `feedAndGetState` is used with a UI to do same things that an +actual `REST` api might do: +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineController.java[tags=snippetB] ---- +==== -Below `feedPageview` is a `REST` method which accepts a post with a -json content. +In the following listing, `feedPageview` is a `REST` method that accepts a post with +JSON content. +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineController.java[tags=snippetC] ---- +==== -Below `feedMachine` will send event into a `StateMachine` and persists -its state using a `StateMachinePersister`. +In the following listing, `feedMachine` sends an event into a `StateMachine` and persists +its state by using a `StateMachinePersister`: +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineController.java[tags=snippetD] ---- +==== -Below `resetStateMachineFromStore` is used to restore a state machine -for a particular user. +The following listing shows a `resetStateMachineFromStore` that is used to restore a state machine +for a particular user: +==== [source,java,indent=0] ---- include::samples/demo/eventservice/StateMachineController.java[tags=snippetE] ---- +==== -As you'd send event using UI, same can be done using a `REST` calls: +As you would usually send an event by using a UI, you can do the same by using `REST` calls, +as the following curl command shows: +==== [source,text] ---- # curl http://localhost:8080/feed -H "Content-Type: application/json" --data '{"user":"joe","id":"VIEW_I"}' ---- +==== -At this point you should have a content in `Redis` with a -`testprefix:joe` key. +At this point, you should have content in Redis with a key of +`testprefix:joe`, as the following example shows: +==== [source,text] ---- $ ./redis-cli 127.0.0.1:6379> KEYS * 1) "testprefix:joe" ---- +==== -Below is a three images when state for `joe` has been changed from -`HOME` to `ITEMS` and when `ADD` action has been executed. +The next three images show when state for `joe` has been changed from +`HOME` to `ITEMS` and when the `ADD` action has been executed. -Send event `ADD`: +The following image the `ADD` event being sent: image::images/sm-eventservice-2.png[width=500] -Now your are still on state `ITEMS` and internal transition caused -extended state variable `COUNT` to increase to `1`. +Now your are still on the `ITEMS` state, and the internal transition caused +the `COUNT` extended state variable to increase to `1`, as the following image shows: image::images/sm-eventservice-3.png[width=500] -Execute below `curl` rest call few times or do it via UI and you -should see `COUNT` variable to increase with every call. +Now you can run the following `curl` rest call a few times (or do it through the UI) and +see the `COUNT` variable increase with every call: +==== [source,text] ---- # curl http://localhost:8080/feed -H "Content-Type: application/json" # --data '{"user":"joe","id":"ADD"}' ---- +==== + +The following image shows the result of these operations: image::images/sm-eventservice-4.png[width=500] [[statemachine-examples-deploy]] == Deploy -Deploy is an example how state machine concepts can be used with an -uml modeling to provide a generic error handling state. This state -machine is a relatively complex example of how various features can be -used to provide a centralized error handling concept. + +The deploy example shows how you can use state machine concepts with +UML modeling to provide a generic error handling state. This state +machine is a relatively complex example of how you can use various features +to provide a centralized error handling concept. +The following image shows the deploy state machine: image::images/model-deployer.png[width=500] -[NOTE] -==== -Above statechart is designed using _Eclipse Papyrus Plugin_ -<> and imported into _Spring StateMachine_ via its uml -model file. _Actions_ and _Guards_ defined in a model are resolved -from a _Spring Application Context_. -==== +NOTE: The preceding state chart was designed by using the Eclipse Papyrus Plugin +(see<>) and imported into Spring StateMachine through the resulting UML +model file. Actions and guards defined in a model are resolved +from a Spring Application Context. -In this state machine scenario we have two different behaviors, -`DEPLOY` and `UNDEPLOY` what user tries to execute. +In this state machine scenario, we have two different behaviors +(`DEPLOY` and `UNDEPLOY`) that user tries to execute. -What is happening a above statechart: +In the preceding state chart: -* In `DEPLOY` state `INSTALL` and `START` states are entered - conditionally. We enter `START` directly if product is already - installed and no need to try to `START` if install fails. -* In `UNDEPLOY` state we enter `STOP` conditionally if application is +* In the `DEPLOY` state, the `INSTALL` and `START` states are entered + conditionally. We enter `START` directly if a product is already + installed and have no need to try to `START` if install fails. +* In the `UNDEPLOY` state, we enter `STOP` conditionally if the application is already running. -* Conditional choices for `DEPLOY` and `UNDEPLOY` are done via - _Choice_ pseudostate within those states and choices are selected - by _Guards_. -* We used _Exit Point_ pseudostates to have more controlled exit from +* Conditional choices for `DEPLOY` and `UNDEPLOY` are done through a + choice pseudostate within those states, and the choices are selected + by guards. +* We use exit point pseudostates to have a more controlled exit from the `DEPLOY` and `UNDEPLOY` states. -* After exit from `DEPLOY` and `UNDEPLOY` we go through a _Junction_ - pseudostate to make a choice if we want to go through `ERROR` state - in case error was added into an _Extended State_. -* Finally we go back to `READY` state to process new requests. +* After exiting from `DEPLOY` and `UNDEPLOY`, we go through a junction + pseudostate to choose whether to go through an `ERROR` state + (if an error was added into an extended state). +* Finally, we go back to the `READY` state to process new requests. -Let's get into actual demo. Run the boot based sample application: +Now we can get to the actual demo. Run the boot based sample application +by running the following command: +==== [source,text,subs="attributes"] ---- # java -jar spring-statemachine-samples-deploy-{revnumber}.jar ---- +==== -In a browser you see something like: +In a browser, you can see something like the following image: image::images/sm-deploy-1.png[width=500] -[IMPORTANT] -==== -As we don't have a real _install_, _start_ or _stop_ functionality we -simulate failures by checking existence of particular message headers. -==== +IMPORTANT: As we do not have real install, start, or stop functionality, we +simulate failures by checking the existence of particular message headers. -Now you can start to send event to a machine and choose various -message headers which will drive a different functionality. +Now you can start to send events to a machine and choose various +message headers to drive functionality. [[statemachine-examples-ordershipping]] == Order Shipping -Order Shipping is an example how state machine concepts can be used -with a process of building a simple order processing system. -Below you can see a statechart driving this order shipping sample. +The order shipping example shows how you can use state machine concepts +to build a simple order processing system. + +The following image shows a state chart that drives this order shipping sample. image::images/sm-ordershipping-1.png[scaledwidth="100%"] -What is happening a above statechart: +In the preceding state chart: -* Machine enters `WAIT_NEW_ORDER` default state. -* Event `PLACE_ORDER` transitions into state `RECEIVE_ORDER` and entry - action `entryReceiveOrder` is executed. -* If order is _OK_ machine goes into two regions one handling order - production and one handling user level payment, else machine goes - into `CUSTOMER_ERROR` final state. -* Machine will loop in a lower region to remind user of a payment +* The state machine enters the `WAIT_NEW_ORDER` (default) state. +* The event `PLACE_ORDER` transitions into the `RECEIVE_ORDER` state and the entry + action (`entryReceiveOrder`) is executed. +* If the order is `OK`, the state machine goes into two regions, one handling order + production and one handling user-level payment. Otherwise, the state machine goes + into `CUSTOMER_ERROR`, which is a final state. +* The state machine loops in a lower region to remind the user to pay until `RECEIVE_PAYMENT` is sent successfully to indicate correct payment. -* Both regions will go into waiting states `WAIT_PRODUCT` and - `WAIT_ORDER` to be joined before parent orthogonal state - `HANDLE_ORDER` is exited. -* Finally machine goes via `SHIP_ORDER` into its final state - `ORDER_SHIPPED`. +* Both regions go into waiting states (`WAIT_PRODUCT` and + `WAIT_ORDER`), where they are joined before the parent orthogonal state + (`HANDLE_ORDER`) is exited. +* Finally, the state machine goes through `SHIP_ORDER` to its final state + (`ORDER_SHIPPED`). -Let's get into actual demo. Run the boot based sample application: +The following command runs the sample: +==== [source,text,subs="attributes"] ---- # java -jar spring-statemachine-samples-ordershipping-{revnumber}.jar ---- +==== -In a browser you see something shown above. You can start by choosing -customer and order and create a machine. +In a browser, you can see something similar to the following image. You can start by choosing +a customer and an order to create a state machine. image::images/sm-ordershipping-2.png[scaledwidth="100%"] -Machine for particular order is now created and you can start to play -with placing an order and sending a payment. Other settings like -_makeProdPlan_, _produce_ and _payment_ allows you to control how +The state machine for a particular order is now created and you can start to play +with placing an order and sending a payment. Other settings (such as +`makeProdPlan`, `produce`, and `payment`) let you control how the state machine works. +The following image shows the state machine waiting for an order: image::images/sm-ordershipping-3.png[scaledwidth="100%"] -Finally you can see what machine does by refreshing a page. +Finally, you can see what machine does by refreshing a page, as the following image shows: image::images/sm-ordershipping-4.png[scaledwidth="100%"] [[statemachine-examples-datajpa]] -== JPA Config -JPA Config is an example how state machine concepts can be used -with a machine configuration kept in a database. This sample is using -embedded _H2_ database with a _H2 Console_ to ease playing with a -database. +== JPA Configuration -This sample uses `spring-statemachine-autoconfigure` which on default -auto-configures repositories and entity classes needed for JPA. -Thus only `@SpringBootApplication` is needed. +The JPA configuration example shows how you can use state machine concepts +with a machine configuration kept in a database. This sample uses +an embedded H2 database with an H2 Console (to ease playing with the +database). +This sample uses `spring-statemachine-autoconfigure` (which, by default, +auto-configures the repositories and entity classes needed for JPA). +Thus, you need only `@SpringBootApplication`. +The following example shows the `Application` class with the `@SpringBootApplication` annotation: + +==== [source,java,indent=0] ---- include::samples/demo/datajpa/Application.java[tags=snippetA] ---- +==== -What comes for a machine config `RepositoryStateMachineModelFactory` -can be used as shown below. +The following example shows how to create a `RepositoryStateMachineModelFactory`: +==== [source,java,indent=0] ---- include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetB] ---- +==== -Let's get into actual demo. Run the boot based sample application: +You can use the following command to run the sample: +==== [source,text,subs="attributes"] ---- # java -jar spring-statemachine-samples-datajpa-{revnumber}.jar ---- +==== -Accessing application via _http://localhost:8080_ brings up a new -constructed machine with every request and you can choose to send -events to a machine. Possible events and machine configuration are +Accessing the application at `http://localhost:8080` brings up a newly +constructed machine for each request. You can then choose to send +events to a machine. The possible events and machine configuration are updated from a database with every request. +The following image shows the UI and the initial events that are created when +this state machine starts: image::images/sm-datajpa-1.png[scaledwidth="100%"] -To access embedded console use _JDBC URL_ `jdbc:h2:mem:testdb` if it's -not already set. +To access the embedded console, you can use the JDBC URL (which is `jdbc:h2:mem:testdb`, if it is +not already set). +The following image shows the H2 console: image::images/sm-datajpa-2.png[scaledwidth="100%"] -From console you can see how database tables look like and modify -those as you wish. +From the console, you can see the database tables and modify +them as you wish. +The following image shows the result of a simple query in the UI: image::images/sm-datajpa-3.png[scaledwidth="100%"] -Now that you got this far you probably wondered how those default -states and transitions got populated into a database. Spring Data -already have a nice trick to auto populate repositories and we simply -use this feature via `Jackson2RepositoryPopulatorFactoryBean`. - +Now that you have gotten this far, you have probably wondered how those default +states and transitions got populated into the database. Spring Data +has a nice trick to auto-populate repositories, and we +used this feature through `Jackson2RepositoryPopulatorFactoryBean`. +The following example shows how we create such a bean: +==== [source,java,indent=0] ---- include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetA] ---- +==== -Actual source for populator data is shown below. +The following listing shows the source of the data with which we populate the database: +==== [source,json,indent=0] ---- include::samples/data.json[] ---- +==== [[statemachine-examples-datapersist]] == Data Persist -Data Persist is an example how state machine concepts can be used -with persisting machine in an external repository. This sample is using -embedded _H2_ database with a _H2 Console_ to ease playing with a -database. Optionally it's also possible to enable _Redis_ or _MongoDB_. -This sample uses `spring-statemachine-autoconfigure` which on default -auto-configures repositories and entity classes needed for JPA. -Thus only `@SpringBootApplication` is needed. +The data persist sample shows how you can state machine concepts +with a persisting machine in an external repository. This sample uses +an embedded H2 database with an H2 Console (to ease playing with the +database). Optionally, you can also enable Redis or MongoDB. +This sample uses `spring-statemachine-autoconfigure` (which, by default, +auto-configures the repositories and entity classes needed for JPA). +Thus, you need only `@SpringBootApplication`. +The following example shows the `Application` class with the `@SpringBootApplication` annotation: + +==== [source,java,indent=0] ---- include::samples/demo/datapersist/Application.java[tags=snippetA] ---- +==== -`StateMachineRuntimePersister` is a new interface working on a runtime -level of a `StateMachine` and its implementation -`JpaPersistingStateMachineInterceptor` is meant to be used with a -_JPA_. +The `StateMachineRuntimePersister` interface works on the runtime +level of a `StateMachine`. Its implementation, +`JpaPersistingStateMachineInterceptor`, is meant to be used with a +JPA. +The following listing creates a `StateMachineRuntimePersister` bean: +==== [source,java,indent=0] ---- include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetA] ---- +==== -Same configuration optionally enabled with _mongo_ profile. +The following example shows how you can use a very similar configuration +to create a bean for MongoDB: +==== [source,java,indent=0] ---- include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetB] ---- +==== -Same configuration optionally enabled with _redis_ profile. +The following example shows how you can use a very similar configuration +to create a bean for Redis: +==== [source,java,indent=0] ---- include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetC] ---- +==== -`StateMachine` can be configured to use runtime persistence by using -`withPersistence` config method. +You can configure `StateMachine` to use runtime persistence by using the +`withPersistence` configuration method. +The following listing shows how to do so: +==== [source,java,indent=0] ---- include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetD] ---- +==== -In this sample we also use `DefaultStateMachineService` which makes it -easier to work with multiple machines +This sample also uses `DefaultStateMachineService`, which makes it +easier to work with multiple machines. +The following listing shows how to create an instance of `DefaultStateMachineService`: +==== [source,java,indent=0] ---- include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetE] ---- +==== -A logic using a `StateMachineService` in this sample is show below. +The following listing shows the logic that drives the `StateMachineService` in this sample: +==== [source,java,indent=0] ---- include::samples/demo/datapersist/StateMachineController.java[tags=snippetA] ---- +==== -Let's get into actual demo. Run the boot based sample application: +You can use the following command to run the sample: +==== [source,text,subs="attributes"] ---- # java -jar spring-statemachine-samples-datapersist-{revnumber}.jar ---- +==== [NOTE] ==== -Profile _jpa_ is enabled on default in _application.yml_. If you want to try -other backends, enable _mongo_ or _redis_ profile. -==== +By default, the `jpa` profile is enabled in `application.yml`. If you want to try +other backends, enable either the `mongo` profile or the `redis` profile. +The following commands specify which profile to use (`jpa` is the default, +but we included it for the sake of completeness): +===== [source,text,subs="attributes"] ---- # java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=jpa # java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=mongo # java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=redis ---- +===== +==== -Accessing application via _http://localhost:8080_ brings up a new -constructed machine with every request and you can choose to send -events to a machine. Possible events and machine configuration are +Accessing the application at http://localhost:8080 brings up a newly +constructed state machine for each request, and you can choose to send +events to a machine. The possible events and machine configuration are updated from a database with every request. -Machines in this sample have a simple configuration with states 'S1' -to 'S6' and events 'E1' to 'E6' transitioning machine between those -states. Two machine identifiers 'datajpapersist1' and -'datajpapersist2' can be used to request particular machine. +The state machines in this sample have a simple configuration with states 'S1' +to 'S6' and events 'E1' to 'E6' to transition the state machine between those +states. You can use two state machine identifiers (`datajpapersist1` and +`datajpapersist2`) to request a particular state machine. +The following image shows the UI that lets you pick a machine and an event and that shows +what happens when you do: image::images/sm-datajpapersist-1.png[scaledwidth="100%"] -Sample defaults to using machine 'datajpapersist1' and goes to its -initial state 'S1'. +The sample defaults to using machine 'datajpapersist1' and goes to its +initial state 'S1'. +The following image shows the result of using those defaults: image::images/sm-datajpapersist-2.png[scaledwidth="100%"] -If events 'E1' and 'E2' are sent into machine 'datajpapersist1' its +If you send events `E1` and `E2` to the `datajpapersist1` state machine, its state is persisted as 'S3'. +The following image shows the result of doing so: image::images/sm-datajpapersist-3.png[scaledwidth="100%"] -If requesting machine 'datajpapersist1' by not sending any events, -machine is restored back to its persisted state 'S3'. +If you then request state machine `datajpapersist1` but send no events, +the state machine is restored back to its persisted state, `S3`. [[statemachine-examples-datajpamultipersist]] == Data Multi Persist -Data Multi Persist is an example which is an extension of two other samples + +The data multi ersist sample is an extension of two other samples: <> and <>. We still keep machine configuration in a database and persist into a -database but this time we also have a machine containing two orthogonal -regions showing how those are persisted independently. This sample -is also using embedded _H2_ database with a _H2 Console_ to ease playing -with a database. +database. However, this time, we also have a machine that contains two orthogonal +regions, to show how those are persisted independently. This sample +also uses an embedded H2 database with an H2 Console (to ease playing +with the database). -This sample uses `spring-statemachine-autoconfigure` which on default -auto-configures repositories and entity classes needed for JPA. -Thus only `@SpringBootApplication` is needed. +This sample uses `spring-statemachine-autoconfigure` (which, by default, +auto-configures the repositories and entity classes needed for JPA). +Thus, you need only `@SpringBootApplication`. +The following example shows the `Application` class with the `@SpringBootApplication` annotation: +==== [source,java,indent=0] ---- include::samples/demo/datajpamultipersist/Application.java[tags=snippetA] ---- +==== -We again create a `StateMachineRuntimePersister`. +As in the other data-driven samples, we again create a `StateMachineRuntimePersister`, +as the following listing shows: +==== [source,java,indent=0] ---- include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetA] ---- +==== -`StateMachineService` makes it easier to work with a machines. +A `StateMachineService` bean makes it easier to work with a machines. +The following listing shows how to create such a bean: +==== [source,java,indent=0] ---- include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetB] ---- +==== -We use data from json to import configuration. +We use JSON data to import the configuration. +The following example creates a bean to do so: +==== [source,java,indent=0] ---- include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetC] ---- +==== -What comes for a machine config `RepositoryStateMachineModelFactory` -can be used as shown below. +The following listing shows how we get a `RepositoryStateMachineModelFactory`: +==== [source,java,indent=0] ---- include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetD] ---- +==== -Let's get into actual demo. Run the boot based sample application: +You can run the sample by using the following command: +==== [source,text,subs="attributes"] ---- # java -jar spring-statemachine-samples-datajpamultipersist-{revnumber}.jar ---- +==== -Accessing application via _http://localhost:8080_ brings up a new -constructed machine with every request and you can choose to send -events to a machine. Possible events and machine configuration are -updated from a database with every request. We also print out -all state machine contexts and current root machine. +Accessing the application at `http://localhost:8080` brings up a newly +constructed machine for each request and lets you send +events to a machine. The possible events and the state machine configuration are +updated from a database for each request. We also print out +all state machine contexts and the current root machine, +as the following image shows: image::images/sm-datajpamultipersist-1.png[scaledwidth="100%"] -Machine _datajpamultipersist1_ is simple flat machine where states _S1_, -_S2_ and _S3_ are transitioned with events _E1_, _E2_ and _E3_ meaning -nothing new there. However machine _datajpamultipersist2_ contains two -regions _R1_ and _R2_ directly under root level, thus a reason why -root level machine really doesn't have a state at all but we still need +The state machine named `datajpamultipersist1` is a simple "`flat`" machine where states `S1`, +`S2` and `S3` are transitioned by events `E1`, `E2`, and `E3` (respectively). +However, the state machine named `datajpamultipersist2` contains two +regions (`R1` and `R2`) directly under the root level. That is why this +root level machine really does not have a state. We need that root level machine to host those regions. -Regions _R1_ and _R2_ in machine _datajpamultipersist2_ contains states -_S10_, _S11_, _S12_ and _S20_, _S21_, _S22_ respectively and events -_E10_, _E11_ and _E12_ are used for region _R1_ and events _E20_, _E21_ -and _E22_ for region _R2_. Lets send events _E10_ and _E20_ to machine -_datajpamultipersist2_ and see how things look like. +Regions `R1` and `R2` in the `datajpamultipersist2` state machine contains states +`S10`, `S11`, and `S12` and `S20`, `S21`, and `S22` (respectively). Events +`E10`, `E11`, and `E12` are used for region `R1` and events `E20`, `E21`, +and event `E22` is used for region `R2`. The following images shows what happens when we +send events `E10` and `E20` to the +`datajpamultipersist2` state machine: image::images/sm-datajpamultipersist-2.png[scaledwidth="100%"] -Regions have their own contexts with their own id's and where the actual -id is postfixed with `#` plus `region id`. As shown below there are -different contexts in a database for regions. +Regions have their own contexts with their own IDs, and the actual +ID is postfixed with `#` and the region ID. As the following image shows, +different regions in a database have different contexts: image::images/sm-datajpamultipersist-3.png[scaledwidth="100%"] [[statemachine-examples-monitoring]] == Monitoring -Monitoring is an example how state machine concepts can be used to -monitor machine transitions and actions. +The monitoring sample shows how you can use state machine concepts to +monitor state machine transitions and actions. +The following listing configures the state machine that we use for this sample: + +==== [source,java,indent=0] ---- include::samples/demo/monitoring/StateMachineConfig.java[tags=snippetA] ---- +==== -Let's get into actual demo. Run the boot based sample application: +You can use the following command to run the sample: +==== [source,text,subs="attributes"] ---- # java -jar spring-statemachine-samples-monitoring-{revnumber}.jar ---- +==== + +The following image shows the state machine's initial state: image::images/sm-monitoring-1.png[scaledwidth="100%"] -Execute some transitions. +The following image shows the state of the state machine after we have +performed some actions: image::images/sm-monitoring-2.png[scaledwidth="100%"] -Metrics can be viewed from Boot. +You can view metrics from Spring Boot by running the following two `curl` +commands (shown with their output): +==== [source,json] ---- # curl http://localhost:8080/actuator/metrics/ssm.transition.duration @@ -1771,9 +2115,12 @@ Metrics can be viewed from Boot. ] } ---- +==== -Tracing can be viewed from Boot. +You can also view tracing from Spring Boot by running the following `curl` +command (shown with its output): +==== [source,json] ---- # curl http://localhost:8080/actuator/statemachinetrace @@ -1813,3 +2160,4 @@ Tracing can be viewed from Boot. } ] ---- +==== diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index a9fd9639..c2284cb1 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -4,308 +4,339 @@ This part of the reference documentation explains the core functionality that Spring Statemachine provides to any Spring based application. -<> the generic configuration support. +It includes the following topics: -<> the use of machine id. +* <> describes the generic configuration support. -<> the generic state machine factory support. +* <> describes the use of machine id. -<> the deferred event support. +* <> describes the generic state machine factory support. -<> the scope support. +* <> describes the deferred event support. -<> the actions support. +* <> describes the scope support. -<> the guard support. +* <> describes the actions support. -<> the extended state support. +* <> describes the guard support. -<> the state context support. +* <> describes the extended state support. -<> the use of triggers. +* <> describes the state context support. -<> the use of state machine listeners. +* <> describes the use of triggers. -<> the generic Spring application context support. +* <> describes the use of state machine listeners. -<> the state machine internal accessor support. +* <> describes the generic Spring application context support. -<> the state machine error handling support. +* <> describes the state machine internal accessor support. -<> the state machine security support. +* <> describes the state machine error handling support. -<> the state machine interceptor support. +* <> describes the state machine security support. -<> the state machine service support. +* <> describes the state machine interceptor support. -<> the state machine persisting support. +* <> describes the state machine service support. -<> the Spring Boot support. +* <> describes the state machine persisting support. -<> the monitoring and trancing support. +* <> describes the Spring Boot support. -<> the distributed state machine support. +* <> describes the monitoring and trancing support. -<> the state machine testing support. +* <> describes the distributed state machine support. -<> the state machine uml modeling support. +* <> describes the state machine testing support. -<> the state machine repository config support. +* <> describes the state machine UML modeling support. + +* <> describes the state machine repository config 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 + +One of the common tasks when using a state machine is to design its +runtime configuration. This chapter focuses 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. -[NOTE] -==== -Configuration examples in this section are not feature complete, i.e. -you always need to have definitions of both states and transitions, -otherwise state machine configuration would be ill-formed. We have +NOTE: Configuration examples in this section are not feature complete. That is, +you always need to have definitions of both states and transitions. +Otherwise, state machine configuration would be ill-formed. We have simply made code snippets less verbose by leaving other needed parts -away. -==== +out. [[statemachine-config-annotations]] -=== Using _enable_ annotations -We use familiar spring _enabler_ annotations to ease configuration. Two -annotations exists, _@EnableStateMachine_ and _@EnableStateMachineFactory_. -These annotations if placed in a _@Configuration_ class will enable -some basic functionality needed by a state machines. +=== Using `enable` Annotations -_@EnableStateMachine_ is used when a configuration wants to create an -instance of a _StateMachine_. Usually _@Configuration_ class extends adapters -`EnumStateMachineConfigurerAdapter` or `StateMachineConfigurerAdapter` which -allows user to override configuration callback methods. We automatically -detect if user is using these adapter classes and modify runtime configuration -logic. +We use two familiar Spring _enabler_ annotations to ease configuration: +`@EnableStateMachine` and `@EnableStateMachineFactory`. +These annotations, when placed in a `@Configuration` class, enable +some basic functionality needed by a state machine. -_@EnableStateMachineFactory_ is used when a configuration wants to create an -instance of a _StateMachineFactory_. +You can use `@EnableStateMachine` when you need a configuration to create an +instance of `StateMachine`. Usually, a `@Configuration` class extends adapters +(`EnumStateMachineConfigurerAdapter` or `StateMachineConfigurerAdapter`), which +lets you override configuration callback methods. We automatically +detect whether you use these adapter classes and modify the runtime configuration +logic accordingly. -[NOTE] -==== -Usage examples of these are shown in below sections. -==== +You can use `@EnableStateMachineFactory` when you need a configuration to create an +instance of a `StateMachineFactory`. + +NOTE: Usage examples of these are shown in below sections. [[statemachine-config-states]] === Configuring States -We'll get into more complex configuration examples a bit later but -let's first start with a something simple. For most simple state -machine you just use `EnumStateMachineConfigurerAdapter` and define -possible states, choose initial and optional end state. +We get into more complex configuration examples a bit later in this guide, but +we first start with something simple. For most simple state +machine, you can use `EnumStateMachineConfigurerAdapter` and define +possible states and choose the initial and optional end states. + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetAA] ---- +==== -It's also possible to use strings instead of enums as states and -events by using `StateMachineConfigurerAdapter` as shown below. Most -of a configuration examples is using enums but generally speaking -strings and enums can be just interchanged. +You can also use strings instead of enumerations as states and +events by using `StateMachineConfigurerAdapter`, as shown in the next example. Most +of the configuration examples ues enumerations, but, generally speaking, +you can interchange strings and enumerations. +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetAB] ---- +==== -[NOTE] -==== -Using enums will bring more safe set of states and event types but -limits possible combinations to compile time. Strings don't have this -limitation and allows user to use more dynamic ways to build state -machine configurations but doesn't allow same level of safety. -==== +NOTE: Using enumerations brings a safer set of states and event types but +limits possible combinations to compile time. Strings do not have this +limitation and let you use more dynamic ways to build state +machine configurations but do not allow same level of safety. === 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. +You can define hierarchical states can by using multiple `withStates()` +calls, where you can use `parent()` to indicate that these +particular states are sub-states of some other state. +The following example shows how to do so: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetB] ---- +==== === Configuring Regions There are no special configuration methods to mark a collection of -states to be part of an orthogonal state. To put it simple, orthogonal -state is created when same hierarchical state machine has multiple set -of states each having a initial state. Because an individual state +states to be part of an orthogonal state. To put it simply, orthogonal +state is created when the same hierarchical state machine has multiple sets +of states, each of which has an initial state. Because an individual state machine can only have one initial state, multiple initial states must mean that a specific state must have multiple independent regions. +The following example shows how to define regions: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetP] ---- +==== -When working with persisting machines with regions or generally -relying any functionalities to reset a machine it may be required -to have a dedicated id for a region itself. On default this id -is just a generated _UUID_. As shown below `StateConfigurer` has -a method `region(String id)` for it. +When persisting machines with regions or generally +relying on any functionalities to reset a machine, you may need +to have a dedicated ID for a region. By default, this ID +is a generated UUID. As the following example shows, `StateConfigurer` has +a method called `region(String id)` that lets you set the ID for a region: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetPP] ---- +==== === 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. +We support three different types of transitions: `external`, +`internal`, and `local`. Transitions are triggered either by a signal +(which is an event sent into a state machine) or by a timer. +The following example shows how to define all three kinds of transitions: +==== [source,java,indent=0] ---- 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 a _StateContext_. +You can use guards to protect state transitions. You can use the `Guard` interface +to do an evaluation where a method has access to a `StateContext`. +The following example shows how to do so: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetD] ---- +==== -In above two different types of guard configurations are used. Firstly a -simple _Guard_ is created as a bean and attached to transition between +In the preceding example, we used two different types of guard configurations. First, we +created a simple `Guard` as a bean and attached it to the 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 -expression based guard is a _SpelExpressionGuard_. This was attached to -transition between states `S2` and `S3`. Both guard in above sample -always evaluate to true. +Second, we used a SPeL expression as a guard to dicate that the +expression must return a `BOOLEAN` value. Behind the scenes, this +expression-based guard is a `SpelExpressionGuard`. We attached it to +the transition between states `S2` and `S3`. Both guards +always evaluate to `true`. [[statemachine-config-actions]] === Configuring Actions -Actions can be defined to be executed with transitions and states -itself. Action is always executed as a result of a transition which -originates from a trigger. +You can define actions to be executed with transitions and states. +An action is always run as a result of a transition that +originates from a trigger. The following example shows how to define an action: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetEA] ---- +==== -In above a single `Action` is defined as bean `action` and associated +In the preceding example, a single `Action` is defined as a bean named `action` and associated with a transition from `S1` to `S2`. +The following example shows how to use an action multiple times: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetEB] ---- - -[NOTE] ==== -Usually you would not define same `Action` instance for different -stages but we did it here not to make too much noise in a code + +NOTE: Usually, you would not define the same `Action` instance for different +stages, but we did it here to not make too much noise in a code snippet. -==== -In above a single `Action` is defined as bean `action` and associated -with states `S1`, `S2` and `S3`. There is more going on there which -needs more clarification: +In the preceding example, a single `Action` is defined by the bean named `action` and associated +with states `S1`, `S2`, and `S3`. We need to clarify what is going on here: -* We defined action for initial state `S1`. -* We defined entry action for state `S1` and left exit action empty. -* We defined exit action for state `S2` and left entry action empty. +* We defined an action for the initial state, `S1`. +* We defined an entry action for state `S1` and left the exit action empty. +* We defined an exit action for state `S2` and left the entry action empty. * We defined a single state action for state `S2`. -* We defined entry action as well as exit action for state `S3`. -* Notice how state `S1` is used twice with `initial()` and `state()` - functions. This is only needed if you want to define entry or exit +* We defined both entry and exit actions for state `S3`. +* Note that state `S1` is used twice with `initial()` and `state()` + functions. You need to do this only if you want to define entry or exit actions with initial state. -[IMPORTANT] -==== -Defining action with `initial()` function only executes particular -action when state machine or sub state is started. Think this action -to be initializing action which is only executed once. Action defined -with `state()` is then executed if state machine is transitioning back +IMPORTANT: Defining action with `initial()` function only runs a particular +action when a state machine or sub state is started. This action +is an initializing action that is run only once. An action defined +with `state()` is then run if the state machine transitions back and forward between initial and non-initial states. -==== ==== State Actions -State actions are executed differently compared to entry and exit -actions simply because execution happens after state has been entered -and can be cancelled if state exit happens before particular action +State actions are run differently compared to entry and exit +actions, because execution happens after state has been entered +and can be cancelled if state exit happens before a particular action has been completed. -State Actions are executed using a normal Spring `TaskScheduler` -wrapped within a `Runnable` which may get cancelled via -`ScheduledFuture`. What this means is that whatever you're doing in your -action, you need to be able to catch `InterruptedException` or generally -periodically check if _Thread_ is interrupted. +State actions are run by using a normal Spring `TaskScheduler` +wrapped within a `Runnable` that can get cancelled through +`ScheduledFuture`. This means that, whatever you do in your +action, you need to be able to catch `InterruptedException` or, more generally, +periodically check whether `Thread` is interrupted. -Below shows typical config which uses default _IMMEDIATE_CANCEL_ which -would simply cancel running task immediately when state is complete. +The following example shows typical configuration that uses default the `IMMEDIATE_CANCEL`, which +would immediately cancel a running task when its state is complete: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests11.java[tags=snippetA] ---- +==== -Policy can be set to _TIMEOUT_CANCEL_ together with a global timeout -per machine. This changes state behaviour to wait action completion -before cancel is requested. +You can set a policy to `TIMEOUT_CANCEL` together with a global timeout +for each machine. This changes state behavior to await action completion +before cancelation is requested. The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests11.java[tags=snippetB] ---- +==== -If _Event_ directly take machine into a state so that event headers -are available to particular action, it is also possible to use dedicated -event header to instruct a specific timeout which is defined in _millis_. -Reserved header value _StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT_ -is used for this. +If `Event` directly takes a machine into a state so that event headers +are available to a particular action, you can also use a dedicated +event header to set a specific timeout (defined in `millis`). +You can use the reserved header value `StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT` +for this purpose. The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests11.java[tags=snippetC] ---- +==== [[statemachine-config-transition-actions-errorhandling]] ==== Transition Action Error Handling -User can always catch exceptions manually but with actions defined for -transitions it is possible to define error action which is called if -exception is raised. Exception is then available from a `StateContext` -passed to that action. +You can always catch exceptions manually. However, with actions defined for +transitions, you can define an error action that is called if an +exception is raised. The exception is then available from a `StateContext` +passed to that action. The following example shows how to create a state +that handles an exception: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetEC] ---- +==== -Similar logic can be done manually for every action if needed. +If need be, you can manually create imilar logic for every action. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetED] ---- +==== [[statemachine-config-state-actions-errorhandling]] ==== State Action Error Handling -Similar logic for error handling what is available for transition -actions is also available for actions defined for state behaviour and -its entry and exit. -For these `StateConfigurer` has methods `stateEntry`, `stateDo` and -`stateExit` to define `error` action together with an actual `action`. +Logic similar to the logic that handles errors in state transitions is also available +for entry to a state and exit from a state. +For these situations, `StateConfigurer` has methods called `stateEntry`, `stateDo`, and +`stateExit`. These methods define an `error` action together with a normal (non-error) `action`. +The following example shows how to use all three methods: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetEE] ---- +==== === Configuring Pseudo States @@ -314,532 +345,593 @@ transitions. Pseudo states are automatically added to state machine as states. ==== Initial State -Simply mark a particular state as initial state by using `initial()` -method. There are two methods where one takes extra argument to define -an initial action. This initial action is good for example initialize -extended state variables. +You can mark a particular state as initial state by using the `initial()` +method. This initial action is good, for example, to initialize +extended state variables. The following example shows how to use the `initial()` method: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetQ] ---- +==== ==== Terminate State -Simply mark a particular state as end state by using `end()` method. -This can be done max one time per individual sub-machine or region. +You can mark a particular state as being an end state by using the `end()` method. +You can do so at most once for each individual sub-machine or region. +The following example shows how to use the `end()` method: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetAA] ---- +==== -==== History State -History state can be defined once for each individual state machine. -You need to choose its state identifier and `History.SHALLOW` or -`History.DEEP` respectively. +==== State History +You can define state history once for each individual state machine. +You need to choose its state identifier and set either `History.SHALLOW` or +`History.DEEP`. The following example uses `History.SHALLOW`: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetR] ---- +==== -Also as shown above, optionally it is possible to define a default +Also, as the preceding example shows, you can optionally define a default transition from a history state into a state vertex in a same machine. -This transition takes place as a default if for example machine has -never been entered, thus no history would be available. If default +This transition takes place as a default if, for example, the machine has +never been entered -- thus, no history would be available. If a default state transition is not defined, then normal entry into a region is -done. This default transition is also used if machine's history is +done. This default transition is also used if a machine's history is a final state. ==== Choice State + Choice needs to be defined in both states and transitions to work -properly. Mark particular state as choice state by using `choice()` -method. This state needs to match source state when transition is +properly. You can mark a particular state as being a choice state by using the `choice()` +method. This state needs to match source state when a transition is configured for this choice. -Transition is configured using `withChoice()` where you define source -state and `first/then/last` structure which is equivalent to normal -`if/elseif/else`. With `first` and `then` you can specify a guard just -like you'd use a condition with `if/elseif` clauses. +You can configure a transition by using `withChoice()`, where you define source +state and a `first/then/last` structure, which is equivalent to a normal +`if/elseif/else`. With `first` and `then`, you can specify a guard just +as you would use a condition with `if/elseif` clauses. -Transition needs to be able to exist so make sure `last` is used. -Otherwise configuration is ill-formed. +A transition needs to be able to exist, so you must make sure to use `last`. +Otherwise, the configuration is ill-formed. The following example shows how to define +a choice state: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetS] ---- +==== -Actions can be executed with both incoming and outgoing transitions of -a choice pseudostate. As seeing from below example, one dummy lambda -action is defined leading into a choice state and one similar dummy -lambda action defined for one outgoing transition where it also -define an error action. +Actions can be run with both incoming and outgoing transitions of +a choice pseudostate. As the following example shows, one dummy lambda +action is defined that leads into a choice state and one similar dummy +lambda action is defined for one outgoing transition (where it also +defines an error action): +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetSSS] ---- +==== -[NOTE] -==== -Junction have same api format meaning actions can be defined +NOTE: Junction have same api format meaning actions can be defined similarly. -==== [[statemachine-config-states-junction]] ==== Junction State -Junction needs to be defined in both states and transitions to work -properly. Mark particular state as choice state by using `junction()` -method. This state needs to match source state when transition is + +You need to define a junction in both states and transitions for it to work +properly. You can mark a particular state as being a choice state by using the `junction()` +method. This state needs to match the source state when a transition is configured for this choice. -Transition is configured using `withJunction()` where you define source -state and `first/then/last` structure which is equivalent to normal -`if/elseif/else`. With `first` and `then` you can specify a guard just -like you'd use a condition with `if/elseif` clauses. +You can configure the transition by using `withJunction()` where you define source +state and a `first/then/last` structure (which is equivalent to a normal +`if/elseif/else`). With `first` and `then`, you can specify a guard as +you would use a condition with `if/elseif` clauses. -Transition needs to be able to exist so make sure `last` is used. -Otherwise configuration is ill-formed. +A transition needs to be able to exist, so you must make sure to use `last`. +Otherwise, the configuration is ill-formed. +The following example uses a junction: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetSS] ---- +==== -[NOTE] -==== -Difference between choice and junction is purely academic as both are -implemented with `first/then/last` structure. However in theory based -on uml model, _choice_ allows only one incoming transition while -_junction_ allows multiple incoming transitions. At a code level +NOTE: The difference between choice and junction is purely academic, as both are +implemented with `first/then/last` structures . However, in theory, based +on UML modeling, `choice` allows only one incoming transition while +`junction` allows multiple incoming transitions. At a code level, the functionality is pretty much identical. -==== ==== Fork State -Fork needs to be defined in both states and transitions to work -properly. Mark particular state as choice state by using `fork()` -method. This state needs to match source state when transition is + +You must define a fork in both states and transitions for it to work +properly. You can mark a particular state as being a choice state by using the `fork()` +method. This state needs to match source state when a transition is configured for this fork. -Target state needs to be a super state or immediate states in -regions. Using a super state as target will take all regions into -initial states. Targeting individual state give more controlled entry -into regions. +The target state needs to be a super state or an immediate state in a +regions. Using a super state as a target takes all regions into +initial states. Targeting individual state gives more controlled entry +into regions. The following example uses a fork: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetT] ---- +==== ==== Join State -Join needs to be defined in both states and transitions to work -properly. Mark particular state as choice state by using `join()` -method. This state doesn't need to match either source states or + +You must define a join in both states and transitions for it to work +properly. You can mark aparticular state as being a choice state by using the `join()` +method. This state does not need to match either source states or a target state in a transition configuration. -Select a target state where transition goes when all source states -has been joined. If you use state hosting regions as source, end -states of a regions are used as joins. Otherwise you can pick any -states from a regions. +You can select a target state where a transition goes when all source states +have been joined. If you use state hosting regions as the source, the end +states of a region are used as joins. Otherwise, you can pick any +states from a region. The following exmaple uses a join: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetU] ---- +==== -It is also possible to have multiple transitions originating from a -join state. It this case it is advised to use guards and define those -so that only one guard evaluates _TRUE_ at any given time as otherwise -transition behaviour is not predicted. This is shown above where guard -simply checks if extended state has variables. +You can also have multiple transitions originate from a +join state. It this case, we advise you to use guards and define your guards +such that only one guard evaluates to `TRUE` at any given time. Otherwise, +transition behavior is not predictable. This is shown in the following example, where the guard +checks whwther the extended state has variables: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetUUU] ---- +==== [[statemachine-config-states-exitentry]] -==== Exit/Entry Point States -Exit and Entry Points can be used to do more controlled exit and entry -from and into a submachines. +==== Exit and Entry Point States +You can use exit and entry points to do more controlled exit and entry +from and into a submachine. +The following example uses the `withEntry` and `withExit` methods to define entry points: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetUU] ---- +==== -As shown above you need to mark particular states as _exit_ and -_entry_ states. Then you create a normal transitions into those states -and also specify _withExit()_ and _withEntry()_ where those states -will exit and entry respectively. +As shown in the preceding, you need to mark particular states as being `exit` and +`entry` states. Then you create a normal transitions into those states +and also specify `withExit()` and `withEntry()`, where those states +exit and entry respectively. [[statemachine-config-commonsettings]] === Configuring Common Settings -Some of a common state machine configuration can be set via a -`ConfigurationConfigurer`. This allows to set `BeanFactory`, -`TaskExecutor`, `TaskScheduler`, autostart flag for a state machine -and register `StateMachineListener` instances. +You can set part of a common state machine configuration by using +`ConfigurationConfigurer`. This you set set `BeanFactory`, +`TaskExecutor`, `TaskScheduler`, and an autostart flag for a state machine≥ +It also lets you register `StateMachineListener` instances. +The following example shows how to use `ConfigurationConfigurer`: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetYA] ---- +==== -State machine `autoStartup` flag is disabled by default because all -instances handling sub-states are controlled by a state machine itself -and cannot be started automatically. Also it is much safer to leave -this decision to a user whether a machine should be started -automatically or not. This flag will only control an autostart of a +By default, the state machine `autoStartup` flag is disabled, because all +instances that handle sub-states are controlled by the state machine itself +and cannot be automatically started. Also, it is much safer to leave +whether a machine should be started +automatically or not to the user. This flag controls only the autostart of a top-level state machine. -Setting `machineId` within a configuration is simply a convenience if -user wants or needs to do it here. +Setting `machineId` within a configuration class is simply a convenience for those times when +you want or need to do it there. -Setting a `BeanFactory`, `TaskExecutor` or `TaskScheduler` exist for -convenience for a user and are also use within a framework itself. +Setting a `BeanFactory`, `TaskExecutor`, or `TaskScheduler` is another +convenience for you, and those settings are also used within the framework itself. Registering `StateMachineListener` instances is also partly for -convenience but is required if user wants to catch callback during a -state machine lifecycle like getting notified of a state machine -start/stop events. Naturally it is not possible to listen a state -machine start events if `autoStartup` is enabled unless listener can -be registered during a configuration phase. +convenience but is required if you want to catch a callback during a +state machine lifecycle, such as getting notified of a state machine's +start and stop events. Note that you cannot listen a state +machine's start events if `autoStartup` is enabled, unless you register a listener +during a configuration phase. -`transitionConflictPolicy` can be used in cases where multiple -transition paths could be selected. One usual use case for this is if -machine contains anonymous transitions leading out from a sub-state -and a parent state and user want to define a policy which one will be +You can use `transitionConflictPolicy` when multiple +transition paths could be selected. One usual use case for this is when a +machine contains anonymous transitions that lead out from a sub-state +and a parent state and you want to define a policy in which one is selected. This is a global setting within a machine instance and -default to _CHILD_. +defaults to `CHILD`. -`DistributedStateMachine` is configured via `withDistributed()` which -allows to set a `StateMachineEnsemble` which if exists automatically -wraps created `StateMachine` with `DistributedStateMachine` and -enables distributed mode. +You can use `withDistributed()` to configure `DistributedStateMachine`. It +lets you set a `StateMachineEnsemble`, which (if it exists) automatically +wraps any created `StateMachine` with `DistributedStateMachine` and +enables distributed mode. The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetYB] ---- +==== -More about distributed states, refer to section <>. +For more about distributed states, see <>. -`StateMachineModelVerifier` is an interface what is used internally to -do some sanity checks for a state machine structure. Its purpose is to +The `StateMachineModelVerifier` interface is used internally to +do some sanity checks for a state machine's structure. Its purpose is to fail fast early instead of letting common configuration errors into a -state machine itself. On default verifier is automatically enabled and +state machine. By default, a verifier is automatically enabled and the `DefaultStateMachineModelVerifier` implementation is used. -With `withVerifier()` user can disable verifier or set a custom one if -needed. +With `withVerifier()`, you can disable verifier or set a custom one if +needed. The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetYC] ---- - -More about config model, refer to section <>. - -[NOTE] -==== -Config methods `withSecurity`, `withMonitoring` and `withPersistence` -are documented in sections <>, <> and -<> respectively. ==== +For more about config model, see <>. + +NOTE: The `withSecurity`, `withMonitoring` and `withPersistence` configuration methods +are documented in <>, <>, and +<>, respectively. + === Configuring Model -`StateMachineModelFactory` is a hook to configure statemachine model -without using a manual configuration. Essentially it is a third party -integration to integrate into a configuration model. -`StateMachineModelFactory` can be hooked into a configuration model by -using a `StateMachineModelConfigurer` as shown above. +`StateMachineModelFactory` is a hook that lets you configure a statemachine model +without using a manual configuration. Essentially, it is a third-party +integration to integrate into a configuration model. +You can hook `StateMachineModelFactory` into a configuration model by +using a `StateMachineModelConfigurer`. The following example shows how to do so: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests8.java[tags=snippetA] ---- +==== -As a custom example `CustomStateMachineModelFactory` would simply -define two states, _S1_ and _S2_ and an event _E1_ between those -states. +The follwoing example uses `CustomStateMachineModelFactory` to +define two states (`S1` and `S2`) and an event (`E1`) between those +states: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests8.java[tags=snippetB] ---- - -[NOTE] ==== -Defining a custom model is usually not what end user is looking for, -although it is possible, however it is a central concept of allowing + +NOTE: Defining a custom model is usually not what people are looking for, +although it is possible. However, it is a central concept of allowing external access to this configuration model. -==== -Example of using this model factory integration can be found from -<>. More generic info about custom model integration can -be found from <>. +You can find an example of using this model factory integration in +<>. You can find more generic info about custom model integration +in <>. [[statemachine-config-thingstoremember]] === Things to Remember -When defining actions, guards or any other references from a -configuration there are things to remember how Spring Framework works -with beans. In below we have defined a normal configuration with -states `S1` and `S2` and 4 transitions between those. All transitions -are either guarded by `guard1` or `guard2`. Pay attention that -`guard1` is created as a real bean because it's annotated with a -_@Bean_, while `guard2` is not. +When defining actions, guards, or any other references from a +configuration, it pays to remember how Spring Framework works +with beans. In the next example, we have defined a normal configuration with +states `S1` and `S2` and four transitions between those. All transitions +are guarded by either `guard1` or `guard2`. You must ensure that +`guard1` is created as a real bean because it is annotated with +`@Bean`, while `guard2` is not. -What this mean is that event `E3` would get `guard2` condition as -`TRUE` and `E4` would get `guard2` condition as `FALSE` as those are -simply coming from a plain method calls to those functions. +This means that event `E3` would get the `guard2` condition as +`TRUE`, and `E4` would get the `guard2` condition as `FALSE`, because those are +coming from plain method calls to those functions. -However because `guard1` is defined as a _@Bean_, it is proxied by a -Spring Framework, thus additional calls to its method will result -only one instantiation of that instance. Event `E1` would get first -proxied instance with condition `TRUE` while event `E2` would get same -instance with `TRUE` condition while method call was defined with -`FALSE`. This is not a Spring State Machine specific behaviour, it's -just how Spring Framework works with _Beans_. +However, because `guard1` is defined as a `@Bean`, it is proxied by the +Spring Framework. Thus, additional calls to its method result in +only one instantiation of that instance. Event `E1` would first get the +proxied instance with condition `TRUE`, while event `E2` would get the same +instance with `TRUE` condition when the method call was defined with +`FALSE`. This is not a Spring State Machine-specific behavior. Rather, it is +how Spring Framework works with beans. +The following example shows how this arrangement works: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests7.java[tags=snippetA] ---- +==== [[sm-machineid]] == State Machine ID -Various classes and interfaces use `machineId` either as a variable or -parameter in a methods. This chapter takes a closer look how + +Various classes and interfaces use `machineId` either as a variable or as a +parameter in methods. This section takes a closer look at how `machineId` relates to normal machine operation and instantiation. -During a runtime `machineId` really don't have any big operational -role except to distinguish machines from each other for example when +During runtime, a `machineId` really does not have any big operational +role except to distinguish machines from each other -- for example, when following logs or doing deeper debugging. Having a lot of different -machine instances quickly gets user lost in translation if there is -no easy way to identify these instances and option to set this -`machineId` was given to a user. +machine instances quickly gets developers lost in translation if there is +no easy way to identify these instances. As a result, we added the option to set the +`machineId`. -=== With @EnableStateMachine -Setting `machineId` via JavaConfig as `mymachine` then exposes that -for logs as shown above. This same `machineId` is also available via -method `StateMachine.getId()`. +=== Using `@EnableStateMachine` +Setting `machineId` in Java configuration as `mymachine` then exposes that value +for logs. This same `machineId` is also available from the +`StateMachine.getId()` method. The following example uses the `machineId` method: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests10.java[tags=snippetA] ---- +==== +The following example of log output shows the `mymachine` ID: + +==== [source,text] ---- 11:23:54,509 INFO main support.LifecycleObjectSupport [main] - started S2 S1 / S1 / uuid=8fe53d34-8c85-49fd-a6ba-773da15fcaf1 / id=mymachine ---- - -[NOTE] -==== -Manual builder <> uses same config -interface meaning behaviour would be equivalent. ==== -=== With @EnableStateMachineFactory -You'll see same `machineId` getting configured if you use a -_StateMachineFactory_ and request a new machine using id. +NOTE: The manual builder (see <>) uses the same configuration +interface, meaning that the behavior is equivalent. +=== Using `@EnableStateMachineFactory` + +You can see the same `machineId` getting configured if you use a +`StateMachineFactory` and request a new machine by using that ID, +as the following example shows: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests10.java[tags=snippetB] ---- +==== -=== With StateMachineModelFactory -Behind a scenes all machine configurations are first translated into a -_StateMachineModel_ so that _StateMachineFactory_ don't need to know -from where configuration originated as machine can be built from -JavaConfig, UML or Repository. If user wants to go crazy a custom -_StateMachineModel_ can also be used which would be a lowest possible -level to define configuration. +=== Using `StateMachineModelFactory` -What all these has to do with a `machineId`? -_StateMachineModelFactory_ also have a method `StateMachineModel -build(String machineId)` which a _StateMachineModelFactory_ +Behind the scenes, all machine configurations are first translated into a +`StateMachineModel` so that `StateMachineFactory` need not know +from where the configuration originated, as a machine can be built from +Java configuration, UML, or a repository. If you want to go crazy, you can also use a custom +`StateMachineModel`, which is the lowest possible +level at which to define configuration. + +What do all of these have to do with a `machineId`? +`StateMachineModelFactory` also has a method with the following signature: +`StateMachineModel build(String machineId)` which a `StateMachineModelFactory` implementation may choose to use. -_RepositoryStateMachineModelFactory_ <> uses +`RepositoryStateMachineModelFactory` (see <>) uses `machineId` to support different configurations in a persistent -storage used via _Spring Data Repository_ interfaces. For example both -_StateRepository_ and _TransitionRepository_ have a method `List -findByMachineId(String machineId)` order to build different states and +store through Spring Data Repository interfaces. For example, both +`StateRepository` and `TransitionRepository` have a method (`List +findByMachineId(String machineId)`), to build different states and transitions by a `machineId`. With -_RepositoryStateMachineModelFactory_ if `machineId` is used as empty -or NULL defaults to repository config(in a backing persistent model) -without known machine id. +`RepositoryStateMachineModelFactory`, if `machineId` is used as empty +or NULL, it defaults to repository configuration (in a backing-persistent model) +without a known machine id. -[NOTE] -==== -_UmlStateMachineModelFactory_ currently doesn't distinguish between -different machine id's as uml source is always coming from a same -file. Thought this may get changed in future releases. -==== +NOTE: Currently, `UmlStateMachineModelFactory` does not distinguish between +different machine IDs, as UML source is always coming from the same +file. This may change in future releases. [[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 + +There are use cases when a state machine needs to be created dynamically +instead of by defining static configuration at compile time. For example, +if there are custom components that use their own state machines +and these components are created dynamically, it is impossible to have +a static state machine that is built during the application start. Internally, +state machines are always built through factory interfaces. This then +gives you an option to use this feature programmatically. +Configuration for a state machine factory is exactly the same as shown in various examples in this document where state machine configuration is hard coded. -=== Factory via Adapter -Actually creating a state machine using _@EnableStateMachine_ will -work via factory so _@EnableStateMachineFactory_ is merely exposing -that factory via its interface. +=== Factory through an Adapter +Actually creating a state machine by using `@EnableStateMachine` +works through a factory, so `@EnableStateMachineFactory` merely exposes +that factory through its interface. The following example uses +`@EnableStateMachineFactory`: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetFA] ---- +==== -Now that you've used _@EnableStateMachineFactory_ to create a factory -instead of a state machine bean, it can be injected and used as is to -request new state machines. +Now that you have used `@EnableStateMachineFactory` to create a factory +instead of a state machine bean, you can inject it and use it (as is) to +request new state machines. The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetL] ---- +==== ==== Adapter Factory Limitations -Current limitation of factory is that all actions and guard it is -associating with created state machine will share a same instances. -This means that from your actions and guard you will need to -specifically handle a case that same bean will be called by a different -state machines. This limitation is something which will be resolved in + +The current limitation of factory is that all the actions and guard with which it +associates a state machine share the same instance. +This means that, from your actions and guard, you need to +specifically handle the case in which the same bean is called by different +state machines. This limitation is something that will be resolved in future releases. -=== State Machine via Builder -Using adapters shown above has a limitation imposed by its -requirement to work via Spring `@Configuration` classes and -application context. While this is a very clear model to configure a -state machine instances it will limit configuration at a compile time -which is not always what a user wants to do. If there is a requirement -to build more dynamic state machines, a simple builder pattern can be -used to construct similar instances. Using strings as states and -events this builder pattern can be used to build fully dynamic state -machines outside of a Spring application context as shown above. +=== State Machine through a Builder +Using adapters (as shown above) has a limitation imposed by its +requirement to work through Spring `@Configuration` classes and the +application context. While this is a very clear model to configure a +state machine, it limits configuration at compile time, +which is not always what a user wants to do. If there is a requirement +to build more dynamic state machines, you can use a simple builder pattern +to construct similar instances. By using strings as states and +events, you can use this builder pattern to build fully dynamic state +machines outside of a Spring application context. The following example +shows how to do so: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetFB] ---- +==== -Builder is using same configuration interfaces behind the scenes that -the `@Configuration` model using adapter classes. Same model goes to -configuring transitions, states and common configuration via builder's -methods. This simply means that whatever you can use with a normal +The builder uses the same configuration interfaces behind the scenes that +the `@Configuration` model uses for adapter classes. The same model goes to +configuring transitions, states, and common configuration through a builder's +methods. This means that whatever you can use with a normal `EnumStateMachineConfigurerAdapter` or `StateMachineConfigurerAdapter` -can be used dynamically via a builder. +you can use dynamically through a builder. -[NOTE] -==== -Currently `builder.configureStates()`, `builder.configureTransitions()` +NOTE: Currently, the `builder.configureStates()`, `builder.configureTransitions()`, and `builder.configureConfiguration()` interface methods cannot be -chained together meaning builder methods needs to be called individually. -==== +chained together, meaning that builder methods need to be called individually. +The following example sets a number of options with a builder: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetFC] ---- +==== -It is important to understand on what cases common configuration needs -to be used with a machines instantiated from a builder. Configurer -returned from a `withConfiguration()` can be used to setup _autoStart_, -_TaskScheduler_, _TaskExecutor_, _BeanFactory_ and additionally register -a _StateMachineListener_. If _StateMachine_ instance returned from -a builder is registered as a bean via `@Bean`, i.e. _BeanFactory_ -is attached automatically and then a default _TaskExecutor_ can be found -from there. If instances are used outside of a spring application context -these methods must be used to setup needed facilities. +You need to understand when common configuration needs +to be used with machines instantiated from a builder. You can use a configurer +returned from a `withConfiguration()` to setup `autoStart`, +`TaskScheduler`, `TaskExecutor`, and `BeanFactory`. You can also use one to register +a `StateMachineListener`. If a `StateMachine` instance returned from +a builder is registered as a bean by using `@Bean`, `BeanFactory` +is attached automatically and you can find the default `TaskExecutor` +from there. If you use instances outside of a spring application context, +you must use these methods to set up the needed facilities. [[sm-deferevents]] == Using Deferred Events -When an event is sent it may fire an `EventTrigger` which then may cause -a transition to happen if a state machine is in a state where trigger is -evaluated successfully. Normally this may lead to a situation where -an event is not accepted and is dropped. However it may be desirable to -postpone this event until a state machine enters other state, in which -it is possible to accept that event. In other words an event simply + +When an event is sent, it may fire an `EventTrigger`, which may then cause +a transition to happen, if a state machine is in a state where a trigger is +evaluated successfully. Normally, this may lead to a situation where +an event is not accepted and is dropped. However, you may wish +postpone this event until a state machine enters another state. In that case, +you can accept that event. In other words, an event arrives at an inconvenient time. Spring Statemachine provides a mechanism for deferring events for later processing. Every state can have a list of deferred events. If an event -in the current state’s deferred event list occurs, the event will be saved +in the current state’s deferred event list occurs, the event is saved (deferred) for future processing until a state is entered that does not list the event in its deferred event list. When such a state is entered, the -state machine will automatically recall any saved events that are no longer -deferred and will then either consume or discard these events. It is possible +state machine automatically recalls any saved events that are no longer +deferred and then either consumes or discards these events. It is possible for a superstate to have a transition defined on an event that is deferred by a substate. Following same hierarchical state machines concepts, the substate -takes precedence over the superstate, the event will be deferred and the -transition for the superstate will not be executed. With orthogonal regions +takes precedence over the superstate, the event is deferred, and the +transition for the superstate is not run. With orthogonal regions, where one orthogonal region defers an event and another accepts the event, the accept takes precedence and the event is consumed and not deferred. -The most obvious use case for event deferring is when an event is causing -a transition into a particular state and state machine is then returned back -to its original state where second event should cause a same -transition. Let's take this with a simple example. +The most obvious use case for event deferring is when an event causes +a transition into a particular state and the state machine is then returned back +to its original state where a second event should cause the same +transition. The following example shows this situation: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests2.java[tags=snippetE] ---- +==== -In above state machine has state _READY_ which indicates that machine is -ready to process events which would take it into a _DEPLOY_ state where the -actual deployment would happen. After deploy actions has been executed machine -is then returned back into a _READY_ state. Sending multiple events in a -_READY_ state is not causing any trouble if machine is using synchronous executor -because event sending would block between event calls. However if executor is using -threads then other events may get lost because machine is no longer in a state where -event could be processed. Thus deferring some of these events allows machine to -preserve these events. +In the preceding example, the state machine has a state of `READY`, which indicates that the machine is +ready to process events that would take it into a `DEPLOY` state, where the +actual deployment would happen. After a deploy action has been run, the machine +is returned back to the `READY` state. Sending multiple events in a +`READY` state does not cause any trouble if the machine is using synchronous executors, +because event sending would block between event calls. However, if the executor uses +threads, other events may get lost, because the machine is no longer in a state where +events can be processed. Thus, deferring some of these events lets the machine +preserve them. The following example shows how to configure such an arrangement: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests2.java[tags=snippetF] ---- +==== -In above state machine which is using nested states instead of a flat -state model, event _DEPLOY_ can be deferred directly in a substate. -It is also showing concept of deferring event _DONE_ in one of a -sub-states which would then override anonymous transition between -_DEPLOY_ and _DONE_ states if state machine happens to be in a -_DEPLOYPREPARE_ state when _DONE_ event is dispatched. In -_DEPLOYEXECUTE_ state _DONE_ event is not deferred, thus event would +In the preceding example, the state machine uses nested states instead of a flat +state model, so the `DEPLOY` event can be deferred directly in a substate. +It also shows the concept of deferring the `DONE` event in a +sub-state that would then override the anonymous transition between +the `DEPLOY` and `DONE` states if the state machine happens to be in a +`DEPLOYPREPARE` state when the `DONE` event is dispatched. In the +`DEPLOYEXECUTE` state when the `DONE` event is not deferred, this event would be handled in a super state. [[sm-scopes]] == Using Scopes -Support for scopes in a state machine is very limited but it is possible -to enable use of _session_ scope using a normal spring `@Scope` annotation. -Firstly if state machine is build manually via a builder and returned into -context as `@Bean`, and secondly via a configuration adapter. Both of -these simply needs an a `@Scope` to be present where _scopeName_ is set to -_session_ and _proxyMode_ to `ScopedProxyMode.TARGET_CLASS`. Examples for -both use cases are shown below. -[TIP] -==== -See sample <> how to use session scoping. -==== +Support for scopes in a state machine is very limited, but you can +enable `session` scope by using a normal Spring `@Scope` annotation in one of two ways: +* If the state machine is built manually by using a builder and returned into the +context as a `@Bean`. +* Through a configuration adapter. + +Both of +these need `@Scope` to be present, with `scopeName` set to +`session` and `proxyMode` set to `ScopedProxyMode.TARGET_CLASS`. The following examples +show both use cases: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests2.java[tags=snippetB] @@ -849,531 +941,600 @@ include::samples/DocsConfigurationSampleTests2.java[tags=snippetB] ---- include::samples/DocsConfigurationSampleTests2.java[tags=snippetC] ---- +==== -Once you have scoped state machine into `session`, autowiring it into -a `@Controller` will give new state machine instance per session. -State machine is then destroyed when `HttpSession` is invalidated. +TIP:See <> for how to use session scoping. +Once you have scoped a state machine into `session`, autowiring it into +a `@Controller` gives a new state machine instance per session. +Each state machine is then destroyed when `HttpSession` is invalidated. +The following example shows how to use a state machine in a controller: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests2.java[tags=snippetD] ---- - -[NOTE] ==== -Using state machines in a `session` scopes needs a careful planning + +NOTE: Using state machines in a `session` scopes needs careful planning, mostly because it is a relatively heavy component. -==== -[NOTE] -==== -Spring Statemachine poms don't have any dependencies to Spring MVC -classes which you will need to work with session scope. But if you're -working with a web application, you've already pulled those deps +NOTE: Spring Statemachine poms have no dependencies to Spring MVC +classes, which you will need to work with session scope. However, if you are +working with a web application, you have already pulled those dependencies directly from Spring MVC or Spring Boot. -==== [[sm-actions]] == Using Actions -Actions are one of the most useful components from user perspective to -interact and collaborate with a state machine. Actions can be executed -in various places in a state machine and its states lifecycle like -entering or exiting states or during a transitions. +Actions are one of the most useful components that you can use to +interact and collaborate with a state machine. You can run actions +in various places in a state machine and its states lifecycle -- for example, +entering or exiting states or during transitions. +The following example shows how to use actions in a state machine: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetVA] ---- +==== -Above `action1` and `action2` beans are attached to states entry and -exit respectively. +In the preceding example, the `action1` and `action2` beans are attached to the `entry` and +`exit` states, respectively. The following example defines those actions (and `action3`): +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetVD] ---- +==== -You can directly implement _Action_ as an anonymous function or create -a your own implementation and define appropriate implementation as a +You can directly implement `Action` as an anonymous function or create +your own implementation and define the appropriate implementation as a bean. -In `action3` a SpEL expression is used to send event *Events.E1* into +In the preceding example, `action3` uses a SpEL expression to send the `Events.E1` event into a state machine. -[NOTE] -==== -_StateContext_ is described in section <>. -==== +NOTE: `StateContext` is described in <>. -=== SpEL Expressions with Actions -It is also possible to use SpEL expressions as a replacement for a -full _Action_ implementation. +=== SpEL Expressions with Actions + +You can also use a SpEL expression as a replacement for a +full `Action` implementation. +// TODO An example would help [[sm-guards]] == Using Guards -Above `guard1` and `guard2` beans are attached to states entry and -exit respectively. +As shown in <>, the `guard1` and `guard2` beans are attached to the entry and +exit states, respectively. +The following example also uses guards on events: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetVB] ---- +==== -You can directly implement _Guard_ as an anonymous function or create -a your own implementation and define appropriate implementation as a -bean. In above sample `guardExpression` is simply checking if extended -state variable `myvar` evaluates to _TRUE_. +You can directly implement `Guard` as an anonymous function or create +your own implementation and define the appropriate implementation as a +bean. In the preceding example, `guardExpression` checkS whether the extended +state variable named `myvar` evaluates to `TRUE`. +The following example implements some sample guards: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetVC] ---- +==== -[NOTE] -==== -_StateContext_ is described in section <>. -==== +NOTE: `StateContext` is described in section <>. === SpEL Expressions with Guards -It is also possible to use SpEL expressions as a replacement for a -full _Guard_ implementation. Only requirement is that expression needs -to return a *Boolean* value to satisfy _Guard_ implementation. This is -demonstrated with a _guardExpression()_ function which takes an + +You can also use a SpEL expression as a replacement for a +full Guard implementation. The only requirement is that the expression needs +to return a `Boolean` value to satisfy the `Guard` implementation. This can be +demonstrated with a `guardExpression()` function that takes an expression as an argument. +// TODO Good spot for an example [[sm-extendedstate]] == Using Extended State -Let's assume that we'd need to create a state machine tracking how -many times a user is pressing a key on a keyboard and then terminate -when keys are pressed 1000 times. Possible but a really naive solution -would be to create a new state for each 1000 key presses. Going -even worse combinations you might suddenly have astronomical number of -states which naturally is not very practical. -This is where extended state variables comes into rescue by not having -a necessity to add more states to drive state machine changes, instead -a simple variable change can be done during a transition. +Assume that you need to create a state machine that tracks how +many times a user is pressing a key on a keyboard and then terminates +when keys are pressed 1000 times. A possible but really naive solution +would be to create a new state for each 1000 key presses. +You might suddenly have an astronomical number of +states, which, naturally, is not very practical. -`StateMachine` has a method `getExtendedState()` which returns an -interface `ExtendedState` which gives an access to extended state -variables. You can access variables directly via a state machine or +This is where extended state variables come to the rescue by not needing +to add more states to drive state machine changes. Instead, +you can do a simple variable change during a transition. + +`StateMachine` has a method called `getExtendedState()`. It returns an +interface called `ExtendedState`, which gives access to extended state +variables. You can access these variables directly through a state machine or through `StateContext` during a callback from actions or transitions. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippet7] ---- +==== -If there is a need to get notified for extended state variable -changes, there are two options; either use `StateMachineListener` and -listen `extendedStateChanged(key, value)` callbacks: - +If you need to get notified for extended state variable +changes, you have two options: either use `StateMachineListener` or +listen for `extendedStateChanged(key, value)` callbacks. The following example +uses the `extendedStateChanged` method: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippet5] ---- +==== -Or implement a Spring Application context listeners for -`OnExtendedStateChanged`. Naturally as mentioned in <> +Alternatively, you can implement a Spring Application context listener for +`OnExtendedStateChanged`. As mentioned in <>, you can also listen all `StateMachineEvent` events. +The following example uses `onApplicationEvent` to listen for state changes: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippet6] ---- +==== [[sm-statecontext]] -== Using StateContext -{sm-statecontext}[_StateContext_] is a one of a most important objects -when working with a state machine as it is passed into various methods -and callbacks to give status of a current state of a state machine and -where it is possibly going. If simplifying things a little it can be -considered to be a snapshot of a current state machine stage where it -is at a time _StateContext_ is passed on. +== Using `StateContext` -[NOTE] -==== -In `Spring Statemachine 1.0.x` _StateContext_ usage were relatively naive -in terms of how it was used to just pass stuff around as a simple `POJO`. -Starting from `Spring Statemachine 1.1.x` its role has been greatly +{sm-statecontext}[`StateContext`] is one of the most important objects +when working with a state machine, as it is passed into various methods +and callbacks to give the current state of a state machine and +where it is possibly going. You can think of it as a +snapshot of the current state machine stage when +is when `StateContext` is retreived. + +NOTE: In Spring Statemachine 1.0.x, `StateContext` usage was relatively naive +in terms of how it was used to pass stuff around as a simple "`POJO`". +Starting from Spring Statemachine 1.1.x, its role has been greatly improved by making it a first class citizen in a state machine. -==== -In overall _StateContext_ can be used as. +You can use `StateContext` to get access to the following: -* Access to current `Message`, `Event` or their - `MessageHeaders` if known. -* Access to state machine `Extended State`. -* Access to `StateMachine` itself. -* Access to possible state machine error. -* Access to current `Transition` if applicable. -* Access to _source_ and _target_ states where state machine is - possibly getting from and going to. -* Access to current `Stage` as described in <>. +* The current `Message` or `Event` (or their `MessageHeaders`, if known). +* The state machine's `Extended State`. +* The `StateMachine` itself. +* To possible state machine errors. +* To the current `Transition`, if applicable. +* The source state of the state machine. +* The target state of the state machine. +* The current `Stage`, as described in <>. -_StateContext_ is passed into various components interacting with user -like `Action` and `Guard`. +`StateContext` is passed into various components, such as +`Action` and `Guard`. [[sm-statecontext-stage]] === Stages -{sm-statecontext-stage}[_Stage_] is representation of a `stage` on -which a state machine is currently interacting with a user. Current + +{sm-statecontext-stage}[`Stage`] is arepresentation of a `stage` on +which a state machine is currently interacting with a user. The currently available stages are `EVENT_NOT_ACCEPTED`, `EXTENDED_STATE_CHANGED`, `STATE_CHANGED`, `STATE_ENTRY`, `STATE_EXIT`, `STATEMACHINE_ERROR`, `STATEMACHINE_START`, `STATEMACHINE_STOP`, `TRANSITION`, -`TRANSITION_START` and `TRANSITION_END` which look very familiar as -those match how user can interact with listeners as described in -<>. +`TRANSITION_START`, and `TRANSITION_END`. These states may look familiar, as +they match how you can interact with listeners (as described in +<>). [[sm-triggers]] == Triggering Transitions -Driving a statemachine is done via transitions which are triggered -by triggers. Currently supported triggers are _EventTrigger_ and -_TimerTrigger_. -=== EventTrigger -_EventTrigger_ is the most useful trigger because it allows user to +Driving a state machine is done by using transitions, which are triggered +by triggers. The currently supported triggers are `EventTrigger` and +`TimerTrigger`. + +=== Using `EventTrigger` + +`EventTrigger` is the most useful trigger, because it lets you directly interact with a state machine by sending events to it. These -events are also called signals. Trigger is added to a transition simply -by associating a state to it during a configuration. +events are also called signals. You can add a trigger to a transition +by associating a state with it during configuration. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetO] ---- -In above example we send an event using two different ways. Firstly we -simply sent a type safe event using state machine api method -`sendEvent(E event)`. Secondly we send event wrapped in a Spring -messaging _Message_ using api method `sendEvent(Message message)` -with a custom event headers. This allows user to add arbitrary extra -information with an event which is then visible to _StateContext_ when -for example user is implementing actions. - -[NOTE] ==== -Message headers are generally passed on until machine runs to + +The preceding example sends an event two different ways. First, it +sends a type-safe event by using the state machine API method called +`sendEvent(E event)`. Second, it sends an event wrapped in a Spring +messaging `Message` by using the API method called `sendEvent(Message message)` +with a custom event headers. This lets us add arbitrary extra +information to an event, which is then visible to `StateContext` when +(for example) you implement actions. + +NOTE: Message headers are generally passed on until machine runs to completion for a specific event. For example if an event is causing transition into a state `A` which have an anonymous transition into a state `B`, original event is available for actions or guards in state `B`. -==== -=== TimerTrigger -_TimerTrigger_ is useful when something needs to be triggered +=== Using `TimerTrigger` + +`TimerTrigger` is useful when something needs to be triggered automatically without any user interaction. `Trigger` is added to a transition by associating a timer with it during a configuration. -Currently there are two types of timers supported, one which fires -continuously and one which fires once a source state is entered. +Currently, there are two types of supported timers, one that fires +continuously and one that fires once a source state is entered. +The following example shows how to use the triggers: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests2.java[tags=snippetA] ---- +==== -In above we have three states, `S1`, `S2` and `S3`. We have a normal +The preceding example has three states: `S1`, `S2`, and `S3`. We have a normal external transition from `S1` to `S2` and from `S1` to `S3` with -events `E1` and `E2` respectively. Interesting parts are when we define +events `E1` and `E2`, respectively. The interesting parts +for working with `TimerTrigger` are when we define internal transitions for source states `S2` and `S3`. -For both transitions we associate `Action` bean `timerAction` where -source state `S2` will use `timer` and `S3` will use `timerOnce`. -Values given are with milliseconds which in these cases mean `1000ms`. +For both transitions, we invoke the `Action` bean (`timerAction`), where +source state `S2` uses `timer` and `S3` uses `timerOnce`. +Values given are in milliseconds (`1000` milliseconds, or one second, in both cases). -Once a state machine receive event `E1` it does a transition -from `S1` to `S2` and timer kicks in. As long as state is kept in `S2` -`TimerTrigger` executes and causes a transition associated with that -state which in this case is the internal transition which has the +Once a state machine receives event `E1`, it does a transition +from `S1` to `S2` and the timer kicks in. When the state is `S2`, +`TimerTrigger` runs and causes a transition associated with that +state -- in this case, the internal transition that has the `timerAction` defined. -Once a state machine receive event `E2` it does a transition -from `S1` to `S3` and timer kicks in. This timer is executed only once -after state is entered after a delay defined in a timer. +Once a state machine receives the `E2`, event it does a transition +from `S1` to `S3` and the timer kicks in. This timer is executed only once +after the state is entered (after a delay defined in a timer). -[NOTE] -==== -Behind a scenes timers are a simple triggers which may cause an -transition to happen. Defining a transition with a `timer()` will keep -firing triggers and only causes transition if source state is active. -Transition with `timerOnce()` is a little different as it will only -trigger after a delay when source state is actually entered. -==== +NOTE: Behind the scenes, timers are simple triggers that may cause a +transition to happen. Defining a transition with a `timer()` keeps +firing triggers and causes transition only if the source state is active. +Transition with `timerOnce()` is a little different, as it +triggers only after a delay when a source state is actually entered. -[TIP] -==== -Use `timerOnce()` if you want something to happen after a delay +TIP: Use `timerOnce()` if you want something to happen after a delay exactly once when state is entered. -==== [[sm-listeners]] -== Listening State Machine Events -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. +== Listening to State Machine Events -You basically have two options, either to listen Spring application -context events or directly attach listener to a state machine. Both of -these basically will provide same information where one is producing -events as event classes and other producing callbacks via a listener -interface. Both of these have pros and cons which will be discussed later. +There are use cases where you want to know what is happening with +a state machine, react to something, or get logging details for +debugging purposes. Spring Statemachine provides interfaces for adding listeners. These listeners +then give an option to get callbacks when various state changes, +actions, and so on happen. + +You basically have two options: listen to Spring application +context events or directly attach a listener to a state machine. Both of +these basically provide the same information. One produces +events as event classes, and the other produces callbacks via a listener +interface. Both of these have pros and cons, which we discuss later. === Application Context Events -Application context events classes are _OnTransitionStartEvent_, -_OnTransitionEvent_, _OnTransitionEndEvent_, _OnStateExitEvent_, -_OnStateEntryEvent_, _OnStateChangedEvent_, _OnStateMachineStart_ and -_OnStateMachineStop_ and others which extends base event class -_StateMachineEvent_ These can be used as is with spring typed -_ApplicationListener_. -_StateMachine_ will send context events via _StateMachineEventPublisher_ -it's set. Default implementation is automatically created if _@Configuration_ -class is annotated with _@EnableStateMachine_. +Application context events classes are `OnTransitionStartEvent`, +`OnTransitionEvent`, `OnTransitionEndEvent`, `OnStateExitEvent`, +`OnStateEntryEvent`, `OnStateChangedEvent`, `OnStateMachineStart`, +`OnStateMachineStop`, and others that extend the base event class, +`StateMachineEvent`. These can be used as is with a Spring +`ApplicationListener`. +`StateMachine` sends context events through `StateMachineEventPublisher`. +The default implementation is automatically created if a `@Configuration` +class is annotated with `@EnableStateMachine`. +The following example gets a `StateMachineApplicationEventListener` +from a bean defined in a `@Configuration` class: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetG] ---- +==== -Context events are also automatically enabled via _@EnableStateMachine_ -with machine builder _StateMachine_ registered as a bean as shown below. +Context events are also automatically enabled by using `@EnableStateMachine`, +with `StateMachine` used to build a machine and registered as a bean, +as the following example shows: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetGG] ---- +==== -=== State Machine Listener -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 +=== Using `StateMachineListener` + +By using `StateMachineListener`, you can either extend it and +implement all callback methods or use the `StateMachineListenerAdapter` +class, which contains stub method implementations and choose which ones to override. +The following example uses the latter approach: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetH] ---- +==== -In above example we simply created our own listener class -_StateMachineEventListener_ which extends -_StateMachineListenerAdapter_. +In the preceding example, we created our own listener class +(`StateMachineEventListener`) that extends +`StateMachineListenerAdapter`. -Listener method `stateContext` gives an access to various -_StateContext_ changes on a different stages. More about about it in -section <>. +The `stateContext` listener method gives access to various +`StateContext` changes on a different stages. You can find more about about it in +<>. -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. +Once you have defined your own listener, you can registered it in a +state machine by using the `addStateListener` method. It is a matter of +flavor whether to hook it up within a spring configuration or do it +manually at any time during the application life-cycle. +The following example shows how to attach a listener: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetM] ---- +==== === Limitations and Problems -Spring application context is not a fastest eventbus 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. +Spring application context is not the fastest event bus out there, so we +advise giving some thought to the rate of events the state machine +sends. For better performance, it may be better to use the +`StateMachineListener` interface. For this specific reason, +you can use the `contextEvents` flag with `@EnableStateMachine` and +`@EnableStateMachineFactory` to disable Spring application context +events, as shown in the preceding section. +The following example shows how to disable Spring application context events: + +==== [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 -either listening its events or using actions with states and -transitions. Time to time this approach would be too limited and -verbose to create interaction with the application a state machine is -working with. For this specific use case we have made a spring style -context integration which easily attach state machine functionality +either listening to its events or using actions with states and +transitions. From time to time, this approach is going be too limited and +verbose to create interaction with the application with which a state machine +works. For this specific use case, we have made a Spring-style +context integration that easily inserts state machine functionality into your beans. -Available annotations has been harmonised to enable access to same -state machine execution points than what is available from +The available annotations has been harmonized to enable access to the same +state machine execution points that are available from <>. -_@WithStateMachine_ annotation can be used to associate a state -machine with an existing bean. Then it is possible to start adding -supported annotations to methods of that bean. +You can use the `@WithStateMachine` annotation to associate a state +machine with an existing bean. Then you can start adding +supported annotations to the methods of that bean. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetA] ---- +==== -It is also possible to attach to any other state machine from an -application context by using annotation `name` field. +You can also attach any other state machine from an +application context by using the annotation `name` field. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetAA] ---- +==== -Sometimes it is more convenient to use _machine id_ which is something -user can set to better identify multiple instances. This id maps to -_getId()_ method in a _StateMachine_ interface. +Sometimes, it is more convenient to use `machine id`, which is something +you can set to better identify multiple instances. This ID maps to +the `getId()` method in the `StateMachine` interface. +The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetAAAA] ---- +==== -_@WithStateMachine_ can also be used as a meta-annotation as shown -above. In this case you could annotate your bean with _WithMyBean_. +You can also use `@WithStateMachine` as a meta-annotation, as shown +in the preceding example. In this case, you could annotate your bean with `WithMyBean`. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetAAA] ---- +==== -[NOTE] -==== -Return type of these methods doesn't matter and is effectively -discard. -==== +NOTE: The return type of these methods does not matter and is effectively +discarded. === Enabling Integration -All features for _@WithStateMachine_ can be enabled by using -annotation _@EnableWithStateMachine_ which simply imports needed -configuration into Spring Application Context. Both -_@EnableStateMachine_ and _@EnableStateMachineFactory_ are already -annotated with this so there is no need for user to add it again. -However if machine is build and configured without a use of -configuration adapters, _@EnableWithStateMachine_ must be used in order -to use features with _@WithStateMachine_. Idea for this is shown -below. +You can enable all the features of `@WithStateMachine` by using +the `@EnableWithStateMachine` annotation, which imports the needed +configuration into the Spring Application Context. Both +`@EnableStateMachine` and `@EnableStateMachineFactory` are already +annotated with this annotation, so there is no need to add it again. +However, if a machine is built and configured without +configuration adapters, you must use `@EnableWithStateMachine` +to use these features with `@WithStateMachine`. +The following example shows how to do so: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetAAAAA] ---- +==== -[IMPORTANT] -==== -If machine is not created as a _Bean_ then it is mandatory to set -_BeanFactory_ for a machine as shown above. Otherwise machine will be -unaware of handlers calling your _@WithStateMachine_ methods. -==== +IMPORTANT: If a machine is not created as a bean, you need to set +`BeanFactory` for a machine, as shown in the prededing example. Otherwise, tge machine is +unaware of handlers that call your `@WithStateMachine` methods. === Method Parameters -Every annotation is supporting exactly same set of possible method -parameters but runtime behaviour is different depending on an -annotation itself and a stage where annotated method is called. To -better understand how context works see + +Every annotation support exactly the same set of possible method +parameters, but runtime behavior differs, depending on the +annotation itself and the stage in which the annotated method is called. To +better understand how context works, see <>. -[NOTE] -==== -For differences between method parameters, see individual annotation -docs below. -==== +NOTE: For differences between method parameters, see the sections that desdribe the +individual annotation, later in this document. -Effectively all annotated methods are called using Spring SPel -expressions which are build dynamically during the process. As to make -this work these expressions needs to have a root object it evaluates -against. This root object is a `StateContext` and we have also made some +Effectively, all annotated methods are called by using Spring SPel +expressions, which are built dynamically during the process. To make +this work, these expressions needs to have a root object (against which they evaluate). +This root object is a `StateContext`. We have also made some tweaks internally so that it is possible to access `StateContext` methods directly without going through the context handle. -Simplest method parameter would naturally be a `StateContext` itself. +The simplest method parameter is a `StateContext` itself. +The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetB] ---- +==== -Rest of the `StateContext` content can be accessed as shown below. -Number of parameters or order of those doesn't matter. +You can access the rest of the `StateContext` content. +Tke number and order of the parameters does not matter. +The following example shows how to access the various parts of the `StateContext` content: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetBB] ---- - -[NOTE] -==== -Instead of getting all event headers with `@EventHeaders` you can use -`@EventHeader` which can bound to a single header. ==== +NOTE: Instead of getting all event headers with `@EventHeaders`, you can use +`@EventHeader`, which can bound to a single header. + +[[state-machine-transition-annotations]] === Transition Annotations -Annotations for transitions are `OnTransition`, `OnTransitionStart` -and `OnTransitionEnd`. -These annotations behave exactly same and let's -see how `OnTransition` is used. Within this annotation a property's -_source_ and _target_ can be used to qualify a transition. If -_source_ and _target_ is left empty then any transition is matched. +The annotations for transitions are `@OnTransition`, `@OnTransitionStart`, +and `@OnTransitionEnd`. +These annotations behave exactly the same. To show how they work, we show +how `@OnTransition` is used. Within this annotation, a property's +you can use `source` and `target` to qualify a transition. If +`source` and `target` are left empty, any transition is matched. +The following example shows how to use the `@OnTransition` annotation +(remember that `@OnTransitionStart` and `@OnTransitionEnd` work the same way): + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetC] ---- +==== -Default _@OnTransition_ annotation can't be used with a state and -event enums user have created due to java language limitations, thus -string representation have to be used. +By default, you cannot use the `@OnTransition` annotation with a state and +event enumerations that you have created, due to Java language limitations. +For this reason, you need to use string representations. -Additionally it is possible to access `Event Headers` and -`ExtendedState` by adding needed arguments to a method. Method +Additionally, you can access `Event Headers` and +`ExtendedState` by adding the needed arguments to a method. The method is then called automatically with these arguments. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetD] ---- +==== -However if you want to have a type safe annotation it is possible to -create a new annotation and use _@OnTransition_ as meta annotation. -This user level annotation can make a reference to actual states and -events enums and framework will try to match these in a same way. - +However, if you want to have a type-safe annotation, you can +create a new annotation and use `@OnTransition` as a meta-annotation. +This user-level annotation can make references to actual states and +events enumerations, and the framework tries to match these in the same way. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetE] ---- +==== -Above we created a _@StatesOnTransition_ annotation which defines -`source` and `target` as a type safe manner. +In the preceding example, we created a `@StatesOnTransition` annotation that defines +`source` and `target` in a type-safe manner. +The following example uses that annotation in a bean: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetF] ---- - -In your own bean you can then use this _@StatesOnTransition_ as is and -use type safe `source` and `target`. +==== === State Annotations -Annotations for states are `OnStateChanged`, `OnStateEntry` and -`OnStateExit`. +The following annotations for states are available: `@OnStateChanged`, `@OnStateEntry`, and +`@OnStateExit`. The following example shows how to use `OnStateChanged` annotation (the +other two work the same way): + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetG] ---- +==== -In a same way that in transition annotations it's possible to define -target and source states. +As you can with <>, you can define +target and source states. The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetGG] ---- +==== -For type safety a new annotation needs to be created for enums with -`OnStateChanged` as a meta annotation. +For type safety, new annotations need to be created for enumerations by using +`@OnStateChanged` as a meta-annotation. The following examples show how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGG] @@ -1383,259 +1544,303 @@ include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGG] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGGG] ---- +==== -Methods for state entry and exit behave in a same way. +The methods for state entry and exit behave in the same way, as the following example shows: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGGGG] ---- +==== === Event Annotation -There is one event related annotation named `OnEventNotAccepted`. It -is possible to listen only specific event by defining `event` property -with the annotation. +There is one event-related annotation. It is named `@OnEventNotAccepted`. +If you specify the `event` property, you can listen for a specific event not being +accepted. If you do not specify an event, you can list for any event not being +accepted. The following example shows both ways to use the `@OnEventNotAccepted` +annotation: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetH] ---- +==== === State Machine Annotations -Annotations for state machine are `OnStateMachineStart`, -`OnStateMachineStop` and `OnStateMachineError`. -During a state machine start and stop lifecycle methods are called. +The following annotations are available for a state machine: `@OnStateMachineStart`, +`@OnStateMachineStop`, and `@OnStateMachineError`. +During a state machine's start and stop, lifecycle methods are called. +The following example shows how to use `@OnStateMachineStart` and +`@OnStateMachineStop` to listen to these events: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetI] ---- +==== -In case a state machine goes into an error with exception, below -annotation is called. +If a state machine goes into an error with exception, `@OnStateMachineStop` +annotation is called. The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetII] ---- +==== === Extended State Annotation -There is one extended state related annotation named -`OnExtendedStateChanged`. It's also possible to listen changes only -for specific `key` changes. +There is one extended state-related annotation. It is named +`@OnExtendedStateChanged`. You can also listen to changes only +for specific `key` changes. The following example shows how to use the +`@OnExtendedStateChanged`, both with and without a `key` property: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests4.java[tags=snippetJ] ---- +==== [[sm-accessor]] -== State Machine Accessor -`StateMachine` is a main interface to communicate with a state machine -itself. Time to time there is a need to get more dynamical and +== Using `StateMachineAccessor` + +`StateMachine` is the main interface for communicating with a state machine. +From time to time, you may need to get more dynamic and programmatic access to internal structures of a state machine and its -nested machines and regions. For these use cases a `StateMachine` is -exposing a functional interface `StateMachineAccessor` which provides +nested machines and regions. For these use cases, `StateMachine` +exposes a functional interface called `StateMachineAccessor`, which provides an interface to get access to individual `StateMachine` and `Region` instances. -`StateMachineFunction` is a simple functional interface which allows -to apply `StateMachineAccess` interface into a state machine. With -jdk7 these will create a little verbose code but with jdk8 lambdas -things look relatively non-verbose. +`StateMachineFunction` is a simple functional interface that lets +you apply the `StateMachineAccess` interface to a state machine. With +JDK 7, these create code that is a little verbose code. However, with JDK 8 lambdas, +the doce is relatively non-verbose. -Method `doWithAllRegions` gives access to all `Region` instances in -a state machine. +The `doWithAllRegions` method gives access to all `Region` instances in +a state machine. The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetZA] ---- +==== -Method `doWithRegion` gives access to single `Region` instance in a -state machine. +The `doWithRegion` method gives access to single `Region` instance in a +state machine. The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetZB] ---- +==== -Method `withAllRegions` gives access to all `Region` instances in -a state machine. +The `withAllRegions` method gives access to all of the `Region` instances in +a state machine. The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetZC] ---- +==== -Method `withRegion` gives access to single `Region` instance in a -state machine. +The `withRegion` method gives access to single `Region` instance in a +state machine. The following example shows how to use it: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetZD] ---- +==== [[sm-interceptor]] -== State Machine Interceptor -Instead of using a `StateMachineListener` interface one option is to -use a `StateMachineInterceptor`. One conceptual difference is that an -interceptor can be used to intercept and stop a current state -change or transition logic. Instead of implementing full interface, -adapter class `StateMachineInterceptorAdapter` can be used to override -default no-op methods. +== Using `StateMachineInterceptor` -[NOTE] -==== -There is one recipe <> and one sample -<> which are related to use of an +Instead of using a `StateMachineListener` interface, you can +use a `StateMachineInterceptor`. One conceptual difference is that you can use an +interceptor to intercept and stop a current state +change or change its transition logic. Instead of implementing a full interface, +you can use an adapter class called `StateMachineInterceptorAdapter` to override +the default no-op methods. + +NOTE: One recipe (<>) and one sample +(<>) are related to using an interceptor. + +You can register an interceptor through `StateMachineAccessor`. The concept of +an interceptor is a relatively deep internal feature and, thus, is not +exposed directly through the `StateMachine` interface. + +The following example shows how to add a `StateMachineInterceptor` and override selected +methods: + ==== - -Interceptor can be registered via `StateMachineAccessor`. Concept of -an interceptor is relatively deep internal feature and thus is not -exposed directly via `StateMachine` interface. - [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetZH] ---- +==== -[NOTE] -==== -More about error handling shown in above example, see section +NOTE: For more about the error handling shown in preceding example, see <>. -==== [[sm-security]] == State Machine Security -Security features are built atop of functionality from a -{spring-security-site}[_Spring Security_]. Security features are + +Security features are built atop of functionality from +{spring-security-site}[Spring Security]. Security features are handy when it is required to protect part of a state machine execution and interaction with it. -[IMPORTANT] -==== -We expect user to be fairly familiar with a _Spring Security_ meaning -we don't go into details of how overall security framework works. For -this read _Spring Security_ reference documentation. -==== +IMPORTANT: We expect you to be fairly familiar with Spring Security, meaning +that we do not go into details of how the overall security framework works. For +this information, you should read the Spring Security reference documentation +(available https://spring.io/projects/spring-security#learn[here]). -First level of defence with a security is naturally protecting events -which really are a driver from user point of view what is going to -happen in a state machine. More fine grained security settings can -then be defined for transitions and actions. This can be think of like -allowing an employee to access a building, walk around it and then -giving more detailed access rights to enter different rooms and allow -to switch lights on and off while being on those rooms. If you trust -your users then event security may be all you need, if you don't, then -more detailed security needs to be applied. +The first level of defense with security is naturally protecting events, +which really drive what is going to +happen in a state machine. You can then define more fine-grained security settings +for transitions and actions. This parallel to giving an employee access to a building +and then giving access to specific rooms within the building and even the ability +to turn on and off the lights in specific rooms. If you trust +your users, event security may be all you need. If not, +you need to apply more detailed security. -More detailed info can be -found from section <>. +You can find more detailed information in <>. -[TIP] -==== -For complete example, see sample <>. -==== +TIP: For a complete example, see the <> sample. === Configuring Security -All generic configurations for security are done in -`SecurityConfigurer` which is obtained from -`StateMachineConfigurationConfigurer`. Security is disabled on -default even if _Spring Security_ classes are -present. +All generic configurations for security are done in +`SecurityConfigurer`, which is obtained from +`StateMachineConfigurationConfigurer`. By default, security is disabled, +even if Spring Security classes are +present. The following example shows how to enable security: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests3.java[tags=snippetD] ---- +==== -If absolutely needed `AccessDecisionManager` for both events and -transitions can be customised. If decision managers are not defined or -are set to `null`, default managers are created internally. +If you absolutely need to, you can customize `AccessDecisionManager` for both events and +transitions. If you do not define decision managers or +set them to `null`, default managers are created internally. === Securing Events -Event security is defined on a global level within a -`SecurityConfigurer`. +Event security is defined on a global level by a `SecurityConfigurer`. +The following example shows how to enable event security: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests3.java[tags=snippetA] ---- +==== -In above configuration we use expression `true` which always evaluates -to _TRUE_. Using an expression which always evaluates to _TRUE_ -would not make sense in a real application but gives a point that -expression needs to return either _TRUE_ or _FALSE_. We also defined -attribute `ROLE_ANONYMOUS` and `ComparisonType` `ANY`. Using attributes -and expressions, see section <>. +In the preceding configuration example, we use an expression of `true`, which always evaluates +to `TRUE`. Using an expression that always evaluates to `TRUE` +would not make sense in a real application but shows the point that +expression needs to return either `TRUE` or `FALSE`. We also defined an +attribute of `ROLE_ANONYMOUS` and a `ComparisonType` of `ANY`. For more about using attributes +and expressions, see <>. === Securing Transitions -Transition security can be defined globally. +You can define transition security globally, as the following example shows. + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests3.java[tags=snippetF] ---- +==== -If security is defined in a transition itself it will override any -globally set security. +If security is defined in a transition itself, it override any +globally set security. The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests3.java[tags=snippetB] ---- +==== -Using attributes and expressions, see section <>. +For more about using attributes and expressions, see <>. === Securing Actions -There are no dedicated security definitions for actions in a state -machine, but it can be accomplished using a global method security -from a _Spring Security_. This simply needs that an `Action` is -defined as a proxied `@Bean` and its `execute` method annotated with a -`@Secured`. +There are no dedicated security definitions for actions in a state +machine, but you can secure actions by using a global method security +from Spring Security. This requires that an `Action` be +defined as a proxied `@Bean` and its `execute` method be annotated with +`@Secured`. The following example shows how to do so: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests3.java[tags=snippetC] ---- +==== -Global method security needs to be enabled with a _Spring Security_ -which is done with along a lines shown below. See _Spring Security_ -reference docs for more details. +Global method security needs to be enabled with Spring Security. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests3.java[tags=snippetE] ---- +==== + +See the Spring Security reference guide (available +https://spring.io/projects/spring-security#learn[here]) for more detail. [[sm-security-attributes-expressions]] === Using Security Attributes and Expressions -Generally there are two ways to define security properties, firstly -using security attributes and secondly using security expressions. + +Generally, you can define security properties in either of two ways: by +using security attributes and by using security expressions. Attributes are easier to use but are relatively limited in terms of functionality. Expressions provide more features but are a little bit -of harder to use. +harder to use. ==== Generic Attribute Usage -On default `AccessDecisionManager` instances for events and -transitions both use a `RoleVoter`, meaning you can use role attributes -familiar from _Spring Security_. -For attributes we have 3 different comparison types, `ANY`, `ALL` and -`MAJORITY` which maps into default access decision managers -`AffirmativeBased`, `UnanimousBased` and `ConsensusBased` respectively. -If custom `AccessDecisionManager` has been defined, comparison type is -effectively discarded as it's only used to create a default manager. +By default, `AccessDecisionManager` instances for events and +transitions both use a `RoleVoter`, meaning you can use role attributes +from Spring Security. + +For attributes, we have three different comparison types: `ANY`, `ALL`, and +`MAJORITY`. These comparison types map onto default access decision managers +(`AffirmativeBased`, `UnanimousBased`, and `ConsensusBased`, respectively). +If you have defined a custom `AccessDecisionManager`, the comparison type is +effectively discarded, as it is used only to create a default manager. ==== Generic Expression Usage -Security expressions needs to return either _TRUE_ or _FALSE_. -The base class for expression root objects is -`SecurityExpressionRoot`. This provides some common expressions which -are available in both transition and event security. +Security expressions must return either `TRUE` or `FALSE`. + +The base class for the expression root objects is +`SecurityExpressionRoot`. It provides some common expressions, which +are available in both transition and event security. The following table +describes the most often used built-in expressions: [[common-expressions]] .Common built-in expressions @@ -1644,15 +1849,15 @@ are available in both transition and event security. | `hasRole([role])` | Returns `true` if the current principal has the specified role. By -default if the supplied role does not start with 'ROLE_' it will be -added. This can be customized by modifying the `defaultRolePrefix` on +default, if the supplied role does not start with `ROLE_`, it is +added. You can customize this by modifying the `defaultRolePrefix` on `DefaultWebSecurityExpressionHandler`. | `hasAnyRole([role1,role2])` | Returns `true` if the current principal has any of the supplied -roles (given as a comma-separated list of strings). By default if the -supplied role does not start with 'ROLE_' it will be added. This can -be customized by modifying the `defaultRolePrefix` on +roles (given as a comma-separated list of strings). By default, if each +supplied role does not start with `ROLE_`, it is added. You can customize this +by modifying the `defaultRolePrefix` on `DefaultWebSecurityExpressionHandler`. | `hasAuthority([authority])` @@ -1660,53 +1865,55 @@ be customized by modifying the `defaultRolePrefix` on | `hasAnyAuthority([authority1,authority2])` | Returns `true` if the current principal has any of the supplied -roles (given as a comma-separated list of strings) +roles (given as a comma-separated list of strings). | `principal` -| Allows direct access to the principal object representing the -current user +| Allows direct access to the principal object that represents the +current user. | `authentication` | Allows direct access to the current `Authentication` object obtained -from the `SecurityContext` +from the `SecurityContext`. | `permitAll` -| Always evaluates to `true` +| Always evaluates to `true`. | `denyAll` -| Always evaluates to `false` +| Always evaluates to `false`. | `isAnonymous()` -| Returns `true` if the current principal is an anonymous user +| Returns `true` if the current principal is an anonymous user. | `isRememberMe()` -| Returns `true` if the current principal is a remember-me user +| Returns `true` if the current principal is a remember-me user. | `isAuthenticated()` -| Returns `true` if the user is not anonymous +| Returns `true` if the user is not anonymous. | `isFullyAuthenticated()` -| Returns `true` if the user is not an anonymous or a remember-me user +| Returns `true` if the user is not an anonymous or a remember-me user. | `hasPermission(Object target, Object permission)` | Returns `true` if the user has access to the provided target for the -given permission. For example, `hasPermission(domainObject, 'read')` +given permission -- for example, `hasPermission(domainObject, 'read')`. | `hasPermission(Object targetId, String targetType, Object permission)` | Returns `true` if the user has access to the provided target for the -given permission. For example, `hasPermission(1, -'com.example.domain.Message', 'read')` +given permission -- for example, `hasPermission(1, +'com.example.domain.Message', 'read')`. |=== ==== Event Attributes -Event id can be matched by using prefix `EVENT_`. For example matching -event `A` would match with attribute `EVENT_A`. +You can match an event ID by using a prefix of `EVENT_`. For example, matching +event `A` would match an attribute of `EVENT_A`. ==== Event Expressions -The base class for expression root object for event is -`EventSecurityExpressionRoot`. This provides access to a `Message` -object which is passed around with eventing. + +The base class for the expression root object for events is +`EventSecurityExpressionRoot`. It provides access to a `Message` +object, which is passed around with eventing. `EventSecurityExpressionRoot` +has only one method, which the following table describes: .Event expressions |=== @@ -1718,14 +1925,18 @@ object which is passed around with eventing. |=== ==== Transition Attributes -Matching transition sources and targets, use prefixes -`TRANSITION_SOURCE_` and `TRANSITION_TARGET_` respectively. + +When matching transition sources and targets, you can use the +`TRANSITION_SOURCE_` and `TRANSITION_TARGET_` prefixes respectively. ==== Transition Expressions -The base class for expression root object for transition is -`TransitionSecurityExpressionRoot`. This provides access to a + +The base class for the expression root object for transitions is +`TransitionSecurityExpressionRoot`. It provides access to a `Transition` -object which is passed around for transition changes. +object, which is passed around for transition changes. +`TransitionSecurityExpressionRoot` has two methods, which the following +table describes: .Transition expressions |=== @@ -1741,781 +1952,818 @@ object which is passed around for transition changes. [[sm-security-details]] === Understanding Security -This section provides more detailed info how security works within a -state machine. Not really something you'd need to know but it is + +This section provides more detailed information about how security works within a +state machine. You may not really need to know, but it is always better to be transparent instead of hiding all the magic what happens behind the scenes. -[NOTE] -==== -Security only makes sense if _State Machine_ is executed in a walled -garden where user don't have direct access to the application thus -could modify Spring Security's `SecurityContext` hold in a thread -local. If user controls the jvm, then effectively there is no security +IMPORTANT: Security makes sense only if Spring Statemachine runs in a walled +garden where user have no direct access to the application and could consequently +modify Spring Security's `SecurityContext` hold in a local thread. +If the user controls the JVM, then effectively there is no security at all. -==== -Integration point for security is done with a -<> which is then added automatically into a -state machine if security is enabled. Specific class is a -`StateMachineSecurityInterceptor` which intercepts events and +The integration point for security is created with a +<>, which is then automatically added into a +state machine if security is enabled. The specific class is +`StateMachineSecurityInterceptor`, which intercepts events and transitions. This interceptor then consults Spring Security's -`AccessDecisionManager` if event can be send or if transition can be -executed. Effectively if decision or vote with a `AccessDecisionManager` -will result an exception, event or transition is denied. +`AccessDecisionManager` to determine whether an event can be sent or whether a transition can be +executed. Effectively, if a decision or a vote with a `AccessDecisionManager` +results in an exception, the event or transition is denied. -Due to way how `AccessDecisionManager` from Spring Security works, we -need one instance of it per secured object. This is a reason why there -is a different manager for events and transitions. In this case events -and transitions are different class objects we're securing. +Due to how `AccessDecisionManager` from Spring Security works, we +need one instance of it per secured object. This is one reason why there +are different managers for events and transitions. In this case, events +and transitions are different class objects that we secure. -On default for events, voters `EventExpressionVoter`, `EventVoter` and -`RoleVoter` are added into a `AccessDecisionManager`. +By default, for events, voters (`EventExpressionVoter`, `EventVoter`, and +`RoleVoter`) are added into an `AccessDecisionManager`. -On default for transitions, voters `TransitionExpressionVoter`, -`TransitionVoter` and `RoleVoter` are added into a `AccessDecisionManager`. +By default, for transitions, voters (`TransitionExpressionVoter`, +`TransitionVoter`, and `RoleVoter`) are added into an `AccessDecisionManager`. [[sm-error-handling]] == State Machine Error Handling -If state machine detects an internal error during a state transition -logic it may throw an exception. Before this exception is processed -internally, user is given a chance to intercept. -Normal `StateMachineInterceptor` can be used to intercept errors and -example of it is shown above. +If a state machine detects an internal error during a state transition +logic, it may throw an exception. Before this exception is processed +internally, you are given a chance to intercept. + +Normally, you can use `StateMachineInterceptor` to intercept errors and the +following listing shows an example of it: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippet1] ---- +==== -When errors are detected, normal event notify mechanism is executed. -This allows to use either `StateMachineListener` or Spring Application -context event listener, more about these read section +When errors are detected, the normal event notify mechanism is executed. +This lets you use either a `StateMachineListener` or a Spring Application +context event listener. For more about these, see <>. -Having said that, a simple listener would look like: +Having said that, the following example shows a simple listener: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippet2] ---- +==== -Generic `ApplicationListener` checking `StateMachineEvent` would look -like. +The following example shows a generic `ApplicationListener` checking `StateMachineEvent`: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippet3] ---- +==== -It's also possible to define `ApplicationListener` directly to -recognize only `StateMachineEvent` instances. +You can also directly define `ApplicationListener` to +recognize only `StateMachineEvent` instances, as the following example shows: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippet4] ---- +==== -[TIP] -==== -Actions defined for transitions also have their own error handling -logic <>. -==== +TIP: Actions defined for transitions also have their own error handling +logic. See <>. [[sm-service]] == State Machine Services -StateMachine services are higher level implementations meant to -provide more user level functionalities to ease normal runtime -operations. Currently only one service interface -<> exists. + +StateMachine services are higher-level implementations meant to +provide more user-level functionalities to ease normal runtime +operations. Currently, only one service interface +(`StateMachineService`) exists. [[sm-service-statemachineservice]] -=== Using StateMachineService -`StateMachineService` is an interface meant to handle running machines -and have a simple methods to 'acquire' and 'release' machines. It has -one default implementation named `DefaultStateMachineService`. +=== Using `StateMachineService` + +`StateMachineService` is an interface that is meant to handle running machines +and have simple methods to "`acquire`" and "`release`" machines. It has +one default implementation, named `DefaultStateMachineService`. [[sm-persist]] -== Persisting State Machine -Traditionally an instance of a state machine is used as is within a -running program. More dynamic behaviour is possible to achieve via -dynamic builders and factories which allows state machine -instantiation on-demand. Building an instance of a state machine is -relatively heavy operation so if there is a need to i.e. handle -arbitrary state change in a database using a state machine we need to +== Persisting a State Machine + +Traditionally, an instance of a state machine is used as is within a +running program. You can achieve more dynamic behavior by using +dynamic builders and factories, which allows state machine +instantiation on-demand. Building an instance of a state machine is a +relatively heavy operation. Consequently, if you need to (for example) handle +an arbitrary state change in a database by using a state machine, you need to find a better and faster way to do it. -Persist feature allows user to save a state of a state machine itself -into an external repository and later reset a state machine based of -serialized state. For example if you have a database table keeping -orders it would be way too expensive to update order state via a state -machine if a new instance would need to be build for every change. -Persist feature allows you to reset a state machine state without +The persist feature lets you save a state of a state machine +into an external repository and later reset a state machine based off the +serialized state. For example, if you have a database table keeping +orders, it would be way too expensive to update an order state with a state +machine if a new instance would need to be built for every change. +The persist feature lets you reset a state machine state without instantiating a new state machine instance. -[NOTE] -==== -There is one recipe <> and one sample -<> which provides more info about +NOTE: There is one recipe (see <>) and one sample +(see <>) that provide more info about persisting states. -==== -While it is possible to build a custom persistence feature using a -`StateMachineListener` it has one conceptual problem. When listener -notifies a change of state, state change has already happened. If a -custom persistent method within a listener fails to update serialized -state in an external repository, state in a state machine and state in -an external repository are then in inconsistent state. +While you can build a custom persistence feature by using a +`StateMachineListener`, it has one conceptual problem. When a listener +notifies about a change of state, the state change has already happened. If a +custom persistent method within a listener fails to update the serialized +state in an external repository, the state in a state machine and the state in +an external repository are then in an inconsistent state. -State machine interceptor can be used instead of where attempt to save -serialized state into an external storage is done during the a state +You can isntead use a state machine interceptor to try to save the +serialized state into external storage during the state change within a state machine. If this interceptor callback fails, -state change attempt will be halted and instead of ending into an -inconsistent state, user can then handle this error manually. Using -the interceptors are discussed in <>. +you can halt the state change attempt and, instead of ending in an +inconsistent state, you can then handle this error manually. See +<> for how to use interceptors. [[sm-persist-statemachinecontext]] -=== Using StateMachineContext -It is impossible to persist a _StateMachine_ using normal java -serialization as object graph is too rich and contains too much -dependencies into other Spring context classes. `StateMachineContext` -is a runtime representation of a state machine which can be used to +=== Using `StateMachineContext` + +You cannot persist a `StateMachine` by using normal java +serialization, as the object graph is too rich and contains too many +dependencies on other Spring context classes. `StateMachineContext` +is a runtime representation of a state machine that you can use to restore an existing machine into a state represented by a particular `StateMachineContext` object. `StateMachineContext` contains two different ways to include information -for a child contexts. These are generally used when machine contains -orthogonal regions. Firstly context can have a list of child context -as is which takes a presence if exists, secondly it is possible -to include a list of references which are used if raw context childs -are not in place. These child references are really only way to +for a child context. These are generally used when a machine contains +orthogonal regions. First, a context can have a list of child contexts +that can be used as is if they exist. Second, you can +include a list of references that are used if raw context children +are not in place. These child references are really the only way to persist a machine where multiple parallel regions are running independently. -[TIP] -==== -There is a sample <> showing -how parallel regions can be persisted. -==== +TIP: The <> sample shows +how you can persist parallel regions. [[sm-persist-statemachinepersister]] -=== Using StateMachinePersister +=== Using `StateMachinePersister` + Building a `StateMachineContext` and then restoring a state machine -from it has always been a little bit of a black magic if done -manually. Interface `StateMachinePersister` aims to ease these -operations by providing _persist_ and _restore_ methods. Default -implementation of this interface is `DefaultStateMachinePersister` +from it has always been a little bit of "`black magic`" if done +manually. The `StateMachinePersister` interface aims to ease these +operations by providing `persist` and `restore` methods. The default +implementation of this interface is `DefaultStateMachinePersister`. -Usage of a `StateMachinePersister` is easy to demonstrate by following -a snippets from tests. We start by creating to two similar configs for -a state machine `machine1` and `machine2`. We could build different -machines for this demonstration using various other ways but this -serves a purpose for this case. +We can show how to use a `StateMachinePersister` by following +a snippets from tests. We start by creating two similar configurations +(`machine1` and `machine2`) for a state machine. Note that we could build different +machines for this demonstration in other ways but this way +works for this case. The following example configures the two state machines: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests5.java[tags=snippetA] ---- +==== -As we're using a `StateMachinePersist` we simply create an in-memory +As we are using a `StateMachinePersist` object, we can create an in-memory implementation. -[NOTE] -==== -In-memory sample is just for demonstration purposes, use a real -persistent storage implementations. -==== +NOTE: This in-memory sample is only for demonstration purposes. For real +applications, you should use a real persistent storage implementation. +The following listing shows how to use the in-memory sample: + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests5.java[tags=snippetB] ---- +==== -After we have instantiated two different machines we can transfer -`machine1` into state `S2` via event `E1`, then persist it and restore -`machine2`. +After we have instantiated the two different machines, we can transfer +`machine1` into state `S2` through event `E1`. Then we can persist it and restore +`machine2`. The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests5.java[tags=snippetC] ---- +==== [[sm-persist-redis]] === Using Redis -Support for persisting State Machine into Redis is done via -`RepositoryStateMachinePersist` which implements -`StateMachinePersist`. Specific implementation is a -`RedisStateMachineContextRepository` which uses `kryo` serialization to + +`RepositoryStateMachinePersist` (which implements +`StateMachinePersist`) offers support for persisting a state machine into Redis. + The specific implementation is a +`RedisStateMachineContextRepository`, which uses `kryo` serialization to persist a `StateMachineContext` into `Redis`. -For `StateMachinePersister` we have a redis related -`RedisStateMachinePersister` implementation which takes an instance of -a `StateMachinePersist` and uses _String_ as its context object. +For `StateMachinePersister`, we have a Redis-related +`RedisStateMachinePersister` implementation, which takes an instance of +a `StateMachinePersist` and uses `String` as its context object. -[TIP] -==== -Check sample <> for detailed usage. -==== +TIP: See the <> sample for detailed usage. -`RedisStateMachineContextRepository` will need a -`RedisConnectionFactory` for it to work and we recommend a -`JedisConnectionFactory` for it as seeing from above example. +`RedisStateMachineContextRepository` needs a +`RedisConnectionFactory` for it to work. We recommend using a +`JedisConnectionFactory` for it, as the preceding example shows. [[sm-persist-statemachineruntimepersister]] -=== Using StateMachineRuntimePersister +=== Using `StateMachineRuntimePersister` + `StateMachineRuntimePersister` is a simple extension to -`StateMachinePersist` adding interface level method to get +`StateMachinePersist` that adds an interface-level method to get `StateMachineInterceptor` associated with it. This interceptor is then -required to persist machine during state changes without needing to +required to persist a machine during state changes without needing to stop and start a machine. -Currently there are implementations for this interface for out-of-the-box -supported _Spring Data Repositories_. These are -`JpaPersistingStateMachineInterceptor`, `MongoDbPersistingStateMachineInterceptor` +Currently, there are implementations for this interface for the +supported Spring Data Repositories. These implementations are +`JpaPersistingStateMachineInterceptor`, `MongoDbPersistingStateMachineInterceptor`, and `RedisPersistingStateMachineInterceptor`. -[TIP] -==== -Check sample <> for detailed usage. -==== +TIP: See the <> sample for detailed usage. [[sm-boot]] == Spring Boot Support -Auto-configuration module `spring-statemachine-autoconfigure` contains all -integration logic with _Spring Boot_ providing functionality i.e. for -auto-config and actuators. All what is needed is to have _State -Machine_ as part of a boot application together with this library. + +The auto-configuration module (`spring-statemachine-autoconfigure`) contains all +the logic for integrating with Spring Boot, which provides functionality for +auto-configuration and actuators. All you need is to have this Spring Statemachine +library as part of a boot application. [[sm-boot-monitoring]] === Monitoring and Tracing + `BootStateMachineMonitor` is created automatically and associated with a state machine. `BootStateMachineMonitor` is a custom `StateMachineMonitor` -implementation which integrates with boot's `MeterRegistry` and endpoints -via a custom `StateMachineTraceRepository`. Optionally this auto-configuration -can be disabled by setting key `spring.statemachine.monitor.enabled` to -`false`. Use of this auto-config is shown in sample -<>. +implementation that integrates with Spring Boot's `MeterRegistry` and endpoints +through a custom `StateMachineTraceRepository`. Optionally, you can disable this auto-configuration +by setting the `spring.statemachine.monitor.enabled` key to +`false`. The +<> sample shows how to use this auto-configuration. === Repository Config -Spring Data Repositories and Entity class scanning is auto-configured -automatically for <> if needed classes are found from a -classpath. -Currently supported configs are configured for `JPA`, `Redis` and -`MongoDB`. Repository auto-configuration can be disabled using a -properties `spring.statemachine.data.jpa.repositories.enabled`, +If the required classes are found from the classpath, Spring Data Repositories + and entity class scanning is automatically auto-configured +for <>. + +The currently supported configurations are `JPA`, `Redis`, and +`MongoDB`. You can disable repository auto-configuration by using the +`spring.statemachine.data.jpa.repositories.enabled`, `spring.statemachine.data.redis.repositories.enabled` and -`spring.statemachine.data.mongo.repositories.enabled` respectively. +`spring.statemachine.data.mongo.repositories.enabled` properties, respectively. [[sm-monitoring]] -== Monitoring State Machine -`StateMachineMonitor` can be used to get more information about -durations of how long transitions and actions takes to execute. Below -you can see how this interface is implemented. +== Monitoring a State Machine +You can use `StateMachineMonitor` to get more information about the +durations of how long transitions and actions take to execute. The following listing +shows how this interface is implemented. + +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests9.java[tags=snippetB] ---- +==== -Once you have `StateMachineMonitor` implementation it can be added to -a state machine via configuration as shown below. +Once you have a `StateMachineMonitor` implementation, you can add it to +a state machine through configuration, as the following example shows: +==== [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests9.java[tags=snippetA] ---- +==== -[TIP] -==== -Check sample <> for detailed usage. -==== +TIP: See the <> sample for detailed usage. [[sm-distributed]] == Using Distributed States + Distributed state is probably one of a most complicated concepts of a -Spring State Machine. What exactly is a distributed state? A state -within a single state machine is naturally really simple to understand -but when there is a need to introduce a shared distributed state -through a state machines, things will get a little complicated. +Spring state machine. What exactly is a distributed state? A state +within a single state machine is naturally really simple to understand, +but, when there is a need to introduce a shared distributed state +through a state machine, things get a little complicated. -[NOTE] -==== -Distributed state functionality is still a preview feature and is not +NOTE: Distributed state functionality is still a preview feature and is not yet considered to be stable in this particular release. We expect this -feature to mature towards the first official release. -==== +feature to mature towards its first official release. -For generic configuration support see section -<> and actual usage example see -sample <>. +For information about generic configuration support, see +<>. For an actual usage example, see +the <> sample. -`Distributed State Machine` is implemented via a -`DistributedStateMachine` class which simply wraps an actual instance +A distributed state machine is implemented through a +`DistributedStateMachine` class that wraps an actual instance of a `StateMachine`. `DistributedStateMachine` intercepts communication with a `StateMachine` instance and works with -distributed state abstractions handled via interface -`StateMachineEnsemble`. Depending on an actual implementation -`StateMachinePersist` interface may also be used to serialize a -`StateMachineContext` which contains enough information to reset a +distributed state abstractions handled through the +`StateMachineEnsemble` interface. Depending on the actual implementation, +you can also use the `StateMachinePersist` interface to serialize a +`StateMachineContext`, which contains enough information to reset a `StateMachine`. -While `Distributed State Machine` is implemented via an abstraction, -only one implementation currently exists based on `Zookeeper`. +While a distributed state machine is implemented through an abstraction, +only one implementation currently exists. It is based on Zookeeper. -Here is a generic example of how `Zookeeper` based `Distributed State -Machine` would be configured. +The following example shows how to configure a Zookeeper-based distributed state +machine`: +==== [source,java,indent=0] ---- include::samples/DocsZookeeperSampleTests.java[tags=snippetA] ---- +==== -Current technical documentation of a `Zookeeker` based distributed -state machine can be found from an appendice <>. +You can find the current technical documentation for a Zookeeker-based distributed +state machine <>. + +=== Using `ZookeeperStateMachineEnsemble` -=== ZookeeperStateMachineEnsemble `ZookeeperStateMachineEnsemble` itself needs two mandatory settings, -an instance of `curatorClient` and `basePath`. Client is a -`CuratorFramework` and path is root of a tree in a `Zookeeper`. +an instance of `curatorClient` and a `basePath`. The client is a +`CuratorFramework`, and the path is the root of a tree in a `Zookeeper` instance. -Optionally it is possible to set `cleanState` which defaults to `TRUE` -and will clear existing data if no members exists in an ensemble. Set -this to `FALSE` if you want to preserve distributed state within +Optionally, you can set `cleanState`, which defaults to `TRUE` +and clears existing data if no members exists in an ensemble. You can set +it to `FALSE` if you want to preserve distributed state within application restarts. -Optionally it is possible to set a size of a `logSize` which defaults -to `32` and is used to keep history of state changes. Value of this -setting needs to be a power of two. `32` is generally good default -value but if a particular state machine is left behind more than a -size of a log it is put into error state and disconnected from an -ensemble indicating it has lost its history to reconstruct fully +Optionally, you can set the size of a `logSize` (defaults +to `32`) to a keep history of state changes. The value of this +setting must be a power of two. `32` is generally a good default +value. If a particular state machine is left behind by more than the +size of the log, it is put into an error state and disconnected from the +ensemble, indicating it has lost its history and its ability to fully reconstruct the synchronized status. [[sm-test]] == Testing Support -We have also added a set of utility classes to easy testing of a state -machine instances. These are used in a framework itself but are also + +We have also added a set of utility classes to ease testing of state +machine instances. These are used in the framework itself but are also very useful for end users. -`StateMachineTestPlanBuilder` is used to build a `StateMachineTestPlan` -which then have one method `test()` which runs a plan. -`StateMachineTestPlanBuilder` contains a fluent builder api to add -steps into a plan and during these steps you can send events and check -various conditions like state changes, transitions and extended state +`StateMachineTestPlanBuilder` builds a `StateMachineTestPlan`, +which has one method (called `test()`). That method runs a plan. +`StateMachineTestPlanBuilder` contains a fluent builder API to let you add +steps to a plan. During these steps, you can send events and check +various conditions, such as state changes, transitions, and extended state variables. -Let's take a simple `StateMachine` build using below example: +The following example uses `StateMachineBuilder` to build a state machine: +==== [source,java,indent=0] ---- include::samples/DocsTestSampleTests.java[tags=snippetB] ---- +==== -In below test plan we have two steps, first we check that initial -state `SI` is indeed set, secondly we send an event `E1` and expect -one state change to happen and machine to end up into a state `S1`. +In the following test plan, we have two steps. First, we check that the initial +state (`SI`) is indeed set. Second, we send an event (`E1`) and expect +one state change to happen and expect the machine to end up in a state of `S1`. +The following listing shows the test plan: +==== [source,java,indent=0] ---- include::samples/DocsTestSampleTests.java[tags=snippetA] ---- +==== These utilities are also used within a framework to test distributed -state machine features and multiple machines can be added to a plan. -If multiple machines are added then it is also possible to choose if -event is sent to particular, random or all machines. +state machine features. Note that you can add multiple machines to a plan. +If you add multiple machines, yuo can also choose to +send an event a particular machine, a random machine, or all machines. -Above testing example uses hamcrest imports: +The preceding testing example uses the following Hamcrest imports: +==== [source,java,indent=0] ---- include::samples/DocsTestSampleTests.java[tags=snippetC] ---- +==== -[TIP] -==== -All possible options for expected are documented in javadocs -{sm-statemachinetestplanbuilder-statemachinetestplanstepbuilder}[_StateMachineTestPlanStepBuilder_]. -==== +TIP: All possible options for expected results are documented in the Javadoc for +{sm-statemachinetestplanbuilder-statemachinetestplanstepbuilder}[`StateMachineTestPlanStepBuilder`]. [[sm-papyrus]] == Eclipse Modeling Support + Defining a state machine configuration with UI modeling is supported -via Eclipse Papyrus framework. +through the Eclipse Papyrus framework. -From eclipse wizard create a new _Papyrus Model_ with `UML` _Diagram -Language_. In this example it's named as `simple-machine`. Then you've -given an option to choose various diagram kind's and a `StateMachine -Diagram` must be chosen. +From the Eclipse wizard, you can create a new Papyrus Model with the UML Diagram +Language. In this example, it is named `simple-machine`. Then you +have an option to choose from various diagram kinds, and you must choose a `StateMachine +Diagram`. -We want to create a machine having two states, `S1` and `S2` where -`S1` is initial state. Then event `E1` is created to do a transition -from `S1` to `S2`. In papyrus a machine would then look like something -shown below. +We want to create a machine that has two states (`S1` and `S2`), where +`S1` is the initial state. Then, we need to create event `E1` to do a transition +from `S1` to `S2`. In Papyrus, a machine would then look like something +the following example: +==== image::images/simple-machine.png[width=500] +==== -Behind a scenes a raw uml file would look like. +Behind the scenes, a raw UML file would look like the following example: +==== [source,xml,indent=0] ---- include::samples/simple-machine.uml[] ---- - -[TIP] ==== -When opening existing uml model defined as uml, you'll have three -files, `.di`, `.notation` and `.uml`. If model was not created in your -eclipse's session, it doesn't understand how to open an actual state -chart. This is a known issue in a Papyrus plugin and there is an easy -workaround. In a Papyrus _Perspective_ you'll see _Model Explorer_ for -you model, double click _Diagram StateMachine Diagram_ which will -instruct eclipse to open this specific model in its proper Papyrus + +TIP: When opening an existing model that has been defined as UML, you have three +files: `.di`, `.notation`, and `.uml`. If a model was not created in your +eclipse's session, it does not understand how to open an actual state +chart. This is a known issue in the Papyrus plugin, and there is an easy +workaround. In a Papyrus perspective, you can see a model explorer for +your model. Double click Diagram StateMachine Diagram, which +instructs Eclipse to open this specific model in its proper Papyrus modeling plugin. -==== -=== Using UmlStateMachineModelFactory +=== Using `UmlStateMachineModelFactory` -After uml file is in place in your project, it can be imported into -configuration using `StateMachineModelConfigurer` where +After a UML file is in place in your project, you can import it into your +configuration by using `StateMachineModelConfigurer`, where `StateMachineModelFactory` is associated with a model. -`UmlStateMachineModelFactory` is a special factory which knows how to -process _Eclipse Papyrus_ generated uml structure. Source uml file can -either be given as a Spring `Resource` or a normal location string. +`UmlStateMachineModelFactory` is a special factory that knows how to +process a Eclipse Papyrus_generated UML structure. The source UML file can +either be given as a Spring `Resource` or as a normal location string. +The following example shows how to create an instance of +`UmlStateMachineModelFactory`: +==== [source,java,indent=0] ---- include::samples/DocsUmlSampleTests1.java[tags=snippetA] ---- +==== -As usually _Spring StateMachine_ is working with _Guards_ and -_Actions_ which are defined as bean, those need to be hooked into uml -by its internal modeling structure. In a below sections you will see -how customized bean references are defined within uml definitions. -Thought it is also possible to register particular methods manually +As usual, Spring Statemachine works with guards and +actions, which are defined as beans. Those need to be hooked into UML +by its internal modeling structure. The following sections show +how customized bean references are defined within UML definitions. +Note that it is also possible to register particular methods manually without defining those as beans. -If `UmlStateMachineModelFactory` is created as a bean its -`ResourceLoader` is wired automatically to find registered actions and -guards. It's also possible to manually define a -`StateMachineComponentResolver` which will then be used to find these -components. Factory also have methods _registerAction_ and -_registerGuard_ which can be used to register these components. More -about this in <>. +If `UmlStateMachineModelFactory` is created as a bean, its +`ResourceLoader` is automatically wired to find registered actions and +guards. You can also manually define a +`StateMachineComponentResolver`, which is then used to find these +components. The factory also has _registerAction_ and +_registerGuard_ methods, which you can use to register these components. For more +about this, see <>. -Uml model is relatively loose what comes for the implementation like -_Spring StateMachine_ itself. There are choices what implementation -need to take for uml support as it leaves a lot of features and -functionalities for an implementation to decide. Below sections go -through how _Spring StateMachine_ will implement uml model based on -_Eclipse Papyrus plugin_. +A UML model is relatively loose when it comes to an implementation such as +Spring Statemachine itself. Spring Statemachine leaves how to implement a lot of features and +functionalities up to the actual implementation. The following sections go +through how Spring Statemachine implements UML models based on +the Eclipse Papyrus plugin. [[sm-papyrus-statemachinecomponentresolver]] -==== StateMachineComponentResolver +==== Using `StateMachineComponentResolver` -Below example shows how `UmlStateMachineModelFactory` is defined with -a `StateMachineComponentResolver` which registers a simple functions -`myAction` and `myGuard` respectively. As you notice these components -are not created as beans. +The next example shows how `UmlStateMachineModelFactory` is defined with +a `StateMachineComponentResolver`, which registers the +`myAction` and `myGuard` functions, respectively. Note that these components +are not created as beans. The following listing shows the example: +==== [source,java,indent=0] ---- include::samples/DocsUmlSampleTests1.java[tags=snippetB] ---- +==== -=== Creating Model -Let's start by creating an empty state machine model. +=== Creating a Model + +We start by creating an empty state machine model, shown in the following image: image::images/papyrus-gs-1.png[width=300] -You'll start by creating a new model and giving it a name. +You can start by creating a new model and giving it a name, as the following image shows: image::images/papyrus-gs-2.png[width=300] -Then you need to choose a _StateMachine Diagram_. +Then you need to choose StateMachine Diagram, as follows: image::images/papyrus-gs-3.png[scaledwidth="100%"] -You end up having an empty state machine. +You end up with an empty state machine. -In above sample named `model` you'll end up three files, `model.di`, -`model.notation` and `model.uml` which can then be used in any other -eclipse instance and `model.uml` can be used by importing it into a -_Spring Statemachine_. +In the preceding images, you should have created a sample named `model`. +You should have wound up with three files: `model.di`, +`model.notation`, and `model.uml`. You can then used these files in any other +Eclipse instance. Further, you can import `model.uml` into a +Spring Statemachine. -=== Define States -State identifier is simply coming from a component name in a diagram. -You must have initial state in your machine which is done by adding -_Initial_ and then drawing a transition to your own initial state. +=== Defining States + +The state identifier comes from a component name in a diagram. +You must have an initial state in your machine, which you can do by adding +a root element and then drawing a transition to your own initial state, +as the following image shows: image::images/papyrus-gs-4.png[scaledwidth="100%"] -In above we added one state `S1`, initial state, and draw a transition +In the preceding image, we added a root element and an initial state (`S1`). Then we drew a transition between those two to indicate that `S1` is an initial state. image::images/papyrus-gs-5.png[scaledwidth="100%"] -In above we added a second state `S2` and added a transition between -those two. +In the preceding image, we added a second state (`S2`) and added a transition between +S1 and S2 (indicating that we have two states). -=== Define Events -To associate an event for a transition you need to create a `Signal` -`E1`. Done from _RootElement->New Child->Signal_. +=== Defining Events + +To associate an event with a transition, you need to create a Signal +(`E1`, in this case). To do so, choose RootElement -> New Child -> Signal. +The following image shows the result: image::images/papyrus-gs-6.png[scaledwidth="100%"] -And then `SignalEvent` with defined signal `E1`. Done from -_RootElement->New Child->SignalEvent_. +Then you need to crate a SignalEvent with the new Signal, `E1`. +To do so, choose RootElement -> New Child -> SignalEvent. +The following image shows the result: image::images/papyrus-gs-7.png[scaledwidth="100%"] -Now that you have a `SignalEvent` defined it can be used to associate -a trigger with a transition. More about that in +Now that you have defined a `SignalEvent`, you can use it to associate +a trigger with a transition. For more about that, see <>. -==== Defer Event +==== Deferring an Event -Events can be deferred to get processed in a more appropriate time. In -UML this is done from a state itself. Choose any state and create a -new trigger under _Deferrable trigger_ and choose `SignalEvent` which -matches `Signal` you want to defer. +You can defer events to process them at a more appropriate time. In +UML, this is done from a state itself. Choose any state, create a +new trigger under *Deferrable trigger* and choose the SignalEvent which +matches the Signal you want to defer. [[sm-papyrus-transitions]] -=== Define Transitions -Transition is simply created by drawing _transition_ line between -source and target states. In above we have states `S1` and `S2` and -anonymous transition between those two. We want to associate event +=== Defining Transitions + +You can create a transition by drawing a transition line between the +source and target states. In the preceding images, we have states `S1` and `S2` and an +anonymous transition between the two. We want to associate event `E1` with that transition. We choose a transition, create a new -trigger and define `SignalEventE1` for that. +trigger, and define SignalEventE1 for that, as the following image shows: image::images/papyrus-gs-8.png[scaledwidth="100%"] -This will give you something like shown below. +This gives you something like the arrangement shown in the following image: image::images/papyrus-gs-9.png[scaledwidth="100%"] -[TIP] -==== -If `SignalEvent` is omitted for a transition it becomes an +TIP: If you omit SignalEvent for a transition, it becomes an anonymous transition. -==== -=== Define Timers -Transition can also happen based on timed events. Spring Statemachine +=== Defining Timers + +Transitions can also happen based on timed events. Spring Statemachine support two types of timers, ones which fires continuously on a background and ones which fires once with a delay when state is entered. -Add new `TimeEvent` child to _Model Explorer_, modify _When_ as -expression defined as _LiteralInteger_. Value of it is then timer as -milliseconds. _Is Relative_ is left to _false_ making timer to fire -continuously. +To add a new TimeEvent child to Model Explorer, modify When as an +expression defined as LiteralInteger. The value of it (in milliseconds) becomes the timer. +Leave Is Relative false to make the timer fire continuously. image::images/papyrus-gs-10.png[scaledwidth="100%"] -To define one timed based event when state is entered it's exactly -same as above but _Is Relative_ is now defined as _true_. +To define one timed based event that triggers when a state is entered, the process is exactly +same as described earlier, but leave Is Relative set to true. The following image +shows the result: image::images/papyrus-gs-11.png[scaledwidth="100%"] -Then what is left for user is to pick these time events instead of +Then the user can pick one of these timed events instead of a signal event for a particular transition. [[sm-papyrus-choice]] -=== Define Choice -Choice is simply defined by drawing one incoming transition into a -`CHOICE` states and multiple outgoing transition from it into target -states. Configuration model in our `StateConfigurer` allows to define -_if/elseif/else_ structure but with uml we simply need to work with -individual _Guards_ for outgoing transitions. +=== Defining a Choice -Make sure that guards defined for transitions do not overlap so that -whatever happens, only one guard would evaluate to _TRUE_ at any given +A choice is defined by drawing one incoming transition into a +CHOICE state and drawing multiple outgoing transitions from it to target +states. The configuration model in our `StateConfigurer` lets you define +an if/elseif/else structure. However, with UML, we need to work with +individual Guards for outgoing transitions. + +You must ensure that the guards defined for transitions do not overlap so that, +whatever happens, only one guard evaluates to TRUE at any given time. This gives precise and predictable results for choice branch -evaluation. Also it is advised to leave one transition without a guard +evaluation. Also we recommend leaving one transition without a guard so that at least one transition path is guaranteed. +The following image shows the result of making a choice with three branches: image::images/papyrus-gs-16.png[scaledwidth="100%"] -[NOTE] -==== -Junction is very much same except it allows multiple incoming -transitions. Thus its behaviour compared to choice is purely -academic. Actual logic to select outgoing transition is exactly same. -==== +NOTE: Junction works similarly same, except that it allows multiple incoming +transitions. Thus, its behavior compared to Choice is purely +academic. The actual logic to select the outgoing transition is exactly the same. -=== Define Junction +=== Defining a Junction See <>. -=== Define Entry/Exit -_EntryPoint_ and _ExitPoint_ are used to do controlled entry and exit -with state having sub-states. In a below statechart events `E1` and -`E2` will do a normal state behaviour by entering and exiting state -`S2` where normal state behaviour happens by entering initial state +=== Defining Entry and Exit Points + +You can use EntryPoint and ExitPoint to create controlled entry and exit +with states that have sub-states. In the following state chart, events `E1` and +`E2` have normal state behavior by entering and exiting state +`S2`, where normal state behavior happens by entering initial state `S21`. -Using event `E3` takes machine into _EntryPoint_ `ENTRY` which then -leads into `S22` without activating initial state `S21` at any time. -Similarly _ExitPoint_ `EXIT` with event `E4` controls specific exit -into state `S4` while normal exit behaviour from `S2` would take -machine into state `S3`. While being on a state `S22` you can choose -events `E4` or `E2` to take machine into states `S3` or `S4` -respectively. +Using event `E3` takes the machine into the `ENTRY` EntryPoint, which then +leads to `S22` without activating initial state `S21` at any time. +Similarly the `EXIT` ExitPoint with event `E4` controls the specific exit +into state `S4`, while normal exit behavior from `S2` would take the +machine into state `S3`. While on state `S22`, you can choose from +events `E4` and `E2` to take the machine into states `S3` or `S4`, +respectively. The following image shows the result: image::images/papyrus-gs-17.png[scaledwidth="100%"] -[NOTE] -==== -If state is defined as submachine reference and entry/exit points need -to be used, a _ConnectionPointReference_ has to be defined externally -, its entry/exit reference set to point to a correct entry/exit point -within a submachine reference. Only after that it is possible to -target a transition which correctly links from outside into inside of -a submachine reference. With _ConnectionPointReference_ you may need -to find these settings from _Properties_ -> _Advanced_ -> _UML_ -> -_Entry/Exit_. UML Spec allows to define multiple entries and exits but -with a state machine only one is allowed. -==== +NOTE: If state is defined as a sub-machine reference and you need to use entry and exit points, +you must externally define a ConnectionPointReference, with +its entry and exit reference set to point to a correct entry or exit point +within a submachine reference. Only after that, is it possible to +target a transition that correctly links from the outside to the inside of +a sub-machine reference. With ConnectionPointReference, you may need +to find these settings from Properties -> Advanced -> UML -> +Entry/Exit. The UML specification lets you define multiple entries and exits. However, +with a state machine, only one is allowed. -=== Define History -When working with history states three different concepts are in play. -UML defines a _Deep History_ and a _Shallow History_. _Default History -State_ comes into play when history state is not yet known. These are +=== Defining History States + +When working with history states, three different concepts are in play. +UML defines a Deep History and a Shallow History. The Default History +State comes into play when history state is not yet known. These are represented in following sections. -==== Shallow -_Shallow History_ is simply selected and a transition defined into it. +==== Shallow History + +In the following image, Shallow History is selected and a transition is defined into it: image::images/papyrus-gs-18.png[scaledwidth="100%"] -==== Deep -_Deep History_ is used for state which has other deep nested states, +==== Deep History + +Deep History is used for state that has other deep nested states, thus giving a chance to save whole nested state structure. +The following image shows a definition that uses Deep History: image::images/papyrus-gs-19.png[scaledwidth="100%"] -==== Default +==== Default History + In cases where a Transition terminates on a history when -the state has not been entered before or it had reached its +the state has not been entered before it had reached its final state, there is an option to force a transition to a specific substate, using the default -history mechanism. For this to happen you simply define transition -into this default state. This is would be a transition from `SH` to +history mechanism. For this to happen, you must define a transition +into this default state. This is the transition from `SH` to `S22`. -In a below example state `S22` would be entered if state `S2` has -never been active as its history has never been recorded. If state -`S2` has been active then either `S20` or `S21` would get chosen. +In the following image, state `S22` is entered if state `S2` has +never been active, as its history has never been recorded. If state +`S2` has been active, then either `S20` or `S21` gets chosen. image::images/papyrus-gs-20.png[scaledwidth="100%"] -=== Define Fork/Join -Both _Fork_ and _Join_ are represented as bars in _Papyrus_. As shown -below you need to draw one outgoing transition from `FORK` into state -`S2` which have orthogonal regions. `JOIN` is then reverse where -joined states are collected together via incoming transitions. +=== Defining Forks and Joins + +Both Fork and Join are represented as bars in Papyrus. As shown +in the next image, you need to draw one outgoing transition from `FORK` into state +`S2` to have orthogonal regions. `JOIN` is then the reverse, where +joined states are collected together from incoming transitions. image::images/papyrus-gs-21.png[scaledwidth="100%"] -=== Define Actions -State entry and exit actions can be associated by using a behaviour, -more about this in <>. +=== Defining Actions -==== Initial Action -Initial action as shown in <> is defined -in uml by adding action in transition leading from _Initial State_ -marker into actual state. This _Action_ is then executed when state +You can assoiate swtate entry and exit actions by using a behavior. +For more about this, see <>. + +==== Using an Initial Action + +An initial action (as shown in <>) is defined +in UML by adding an action in the transition that leads from the Initial State +marker into the actual state. This Action is then run when the state machine is started. -=== Define Guards -Guard can be defined by first adding _Constraint_ and then defining -its _Specification_ as _OpaqueExpression_ which works in a same way -than <>. +=== Defining Guards + +You can define a guard by first adding a Constraint and then defining +its Specification as OpaqueExpression, which works in the same way +as <>. [[sm-papyrus-beanref]] -=== Define Bean Reference -When there is a need to make a bean reference in any uml _effect_, -_action_ or _guard_, supported method to do that is via -`FunctionBehavior` or `OpaqueBehavior` where defined language needs to -be `bean` and language body having a bean reference id. +=== Defining a Bean Reference + +When you need to make a bean reference in any UML effect, +action, or guard, you can do so with +`FunctionBehavior` or `OpaqueBehavior`, where the defined language needs to +be `bean` and the language body msut have a bean reference id. [[sm-papyrus-spelref]] -=== Define SpEL Reference -When there is a need to use a _SpEL_ instead of a bean reference in -any uml _effect_, _action_ or _guard_, supported method to do that is via -`FunctionBehavior` or `OpaqueBehavior` where defined language needs to -be `spel` and language body having a SpEL expression. +=== Defining a SpEL Reference + +When you need to use a SpEL expression instead of a bean reference in +any UML effect, action, or guard, you can do so by using +`FunctionBehavior` or `OpaqueBehavior`, where the defined language needs to +be `spel` and the language body must be a SpEL expression. [[sm-papyrus-submachineref]] -=== Using Sub-Machine Reference -Normally when using sub-states those are simply drawn into a state -chart itself. Chart itself may become a little complex and big to -follow so we also support defining sub-state as a statemachine +=== Using a Sub-Machine Reference + +Normally, when you use sub-states, you draw those into the state +chart itself. The chart may become too complex and big to +follow, so we also support defining a sub-state as a state machine reference. -First create a _New Diagram_ and give it a name i.e. _SubStateMachine -Diagram_. +To create a sub-machine reference, you must first create a new diagram and give it a name +(for example, SubStateMachine Diagram). The following image shows the menu choices to use: image::images/papyrus-gs-12.png[scaledwidth="100%"] -Give new diagram a design you need. +Give the new diagram the design you need. +The following image shows a simple design as an example: image::images/papyrus-gs-13.png[scaledwidth="100%"] -From state you want to link(in this case state `S2`), click -`Submachine` field and choose your linked machine, i.e. -_SubStateMachine_. +From the state you want to link (in this case,m state `S2`), click the +`Submachine` field and choose your linked machine (in our example, +`SubStateMachine`). image::images/papyrus-gs-14.png[scaledwidth="100%"] -Finally you'll see that state `S2` is linked to `SubStateMachine` as a +Finally, in the following image, you can see that state `S2` is linked to `SubStateMachine` as a sub-state. image::images/papyrus-gs-15.png[scaledwidth="100%"] [[sm-repository]] == Repository Support + This section contains documentation related to using 'Spring Data -Repositories' used in State Machine. +Repositories' in Spring Statemachine. [[sm-repository-config]] -=== Repository Config -It is also possible to keep machine configuration in an external -storage where it will be loaded on demand instead of creating a static -configuration either using _JavaConfig_ or _UML_ based config. This -integration works via _Spring Data Repository_ abstraction. +=== Repository Configuration -We have created special `StateMachineModelFactory` implementation -called `RepositoryStateMachineModelFactory` which is able to use base -repository interfaces `StateRepository`, `TransitionRepository`, -`ActionRepository` and `GuardRepository` accompanied with base entity -interfaces `RepositoryState`, `RepositoryTransition`, -`RepositoryAction` and `RepositoryGuard` respectively. +You can keep machine configuration in external +storage, from which it can be loaded on demand, instead of creating a static +configuration by using either Java configuration or UML-based configuration. This +integration works through a Spring Data Repository abstraction. -Due to way how _Entities_ and _Repositories_ work in a _Spring Data_, -from a user perspective read access can be fully abstracted as it is -done in `RepositoryStateMachineModelFactory` as there is no need to -know what is a real mapped _Entity_ class _Repository_ is working -with. Writing into a _Repository_ is always dependant of using a real -_Repository_ specific _Entity_ class. From machine configuration point -of view we don't need to know these, meaning we don't need to know -actual implementation whether that is _JPA_, _Redis_ or anything else -what _Spring Data_ supports. Using a real _Repository_ related -_Entity_ class comes into play when you manually try to write new +We have created a special `StateMachineModelFactory` implementation +called `RepositoryStateMachineModelFactory`. It can use the base +repository interfaces (`StateRepository`, `TransitionRepository`, +`ActionRepository` and `GuardRepository`) and base entity +interfaces (`RepositoryState`, `RepositoryTransition`, +`RepositoryAction`, and `RepositoryGuard`). + +Due to way how entities and repositories work in Spring Data, +from a user perspective, read access can be fully abstracted as it is +done in `RepositoryStateMachineModelFactory`. There is no need to +know the actual mapped entity class with which a repository works. +Writing into a repository is always dependent on using a real +repository-specific entity class. From a machine-configuration point +of view, we do not need to know these, meaning that we do not need to know +whether the actual implementation is JPA, Redis, or anything else +that Spring Data supports. Using an actual repository-related +entity class comes into play when you manually try to write new states or transitions into a backed repository. -[TIP] -==== -Entity classes for _RepositoryState_ and _RepositoryTransition_ have -`machineId` field which is in users disposal and can be used to -differentiate between configurations for example if machines are built -via _StateMachineFactory_. -==== +TIP: Entity classes for `RepositoryState` and `RepositoryTransition` have a +`machineId` field, which is at your disposal and can be used to +differentiate between configurations -- for example, if machines are built +via `StateMachineFactory`. -Actual out of a box implementations are documented in below sections -where images below are uml equivalent statecharts of a repository -configs. +Actual implementations are documented in later sections. +The following images are UML-equivalent state charts of repository +configurations. [[image-sm-repository-simplemachine]] image::images/sm-repository-simplemachine.png[scaledwidth="100%", title="SimpleMachine"] @@ -2528,173 +2776,203 @@ image::images/sm-repository-showcasemachine.png[scaledwidth="100%", title="Showc [[sm-repository-config-jpa]] ==== JPA -Actual _Repository_ implementations for a _JPA_ are -`JpaStateRepository`, `JpaTransitionRepository`, `JpaActionRepository` -and `JpaGuardRepository` which are backed by -_Entity_ classes `JpaRepositoryState`, `JpaRepositoryTransition`, -`JpaRepositoryAction` and `JpaRepositoryGuard` respectively. -[IMPORTANT] -==== -Version '1.2.8' unfortunately had to made a change into JPA's _Entity_ -model regarding used table names. Previously generated table names -always had a prefix 'JPA_REPOSITORY_' derived from _Entity_ class +The actual repository implementations for JPA are +`JpaStateRepository`, `JpaTransitionRepository`, `JpaActionRepository`, +and `JpaGuardRepository`, which are backed by the +entity classes `JpaRepositoryState`, `JpaRepositoryTransition`, +`JpaRepositoryAction`, and `JpaRepositoryGuard`, respectively. + +IMPORTANT: Unfortunately, version '1.2.8' had to make a change in JPA's entity +model regarding used table names. Previously, generated table names +always had a prefix of `JPA_REPOSITORY_`, derived from entity class names. As this caused breaking issues with databases imposing -restrictions on database object lengths, all _Entity_ classes have -spesific definitions to force table names. For example -'JPA_REPOSITORY_STATE' is now simple 'STATE' and so on with other -_Entity_ classes. +restrictions on database object lengths, all entity classes have +spesific definitions to force table names. For example, +`JPA_REPOSITORY_STATE` is now 'STATE' -- and so on with other +ntity classes. + +The generic way to update states and transitions manually for JPA is shown +in the following example (equivalent to the machine shown in +<>): + ==== - -Generic way to update states and transition manually for jpa is shown -below. This is equivalent to machine shown in -<>. - [source,java,indent=0] ---- include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetA] ---- +==== -This is equivalent to machine shown in +The following example is also equivalent to the machine shown in <>. +==== [source,java,indent=0] ---- include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetB] ---- +==== -This is equivalent to machine shown in -<>. - -First you access all repositories. +First, you must access all repositories. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC1] ---- +==== -Create actions and guards. +Second, you mus create actions and guards. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC2] ---- +==== -Create states. +Third, you must create states. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC3] ---- +==== -Finally create transitions. +Fourth and finally, you must create transitions. +The following example shows how to do so: +==== [source,java,indent=0] ---- include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC4] ---- +==== -Complete example can be found from sample -<>. This example is also showing how -repository can be pre-populated from existing _json_ file having a +You can find a complete example +<>. This example also shows how you can +pre-populate a repository from an existing JSON file that has definitions for entity classes. [[sm-repository-config-redis]] ==== Redis -Actual _Repository_ implementations for a _Redis_ are -`RedisStateRepository`, `RedisTransitionRepository`, `RedisActionRepository` -and `RedisGuardRepository` which are backed by -_Entity_ classes `RedisRepositoryState`, `RedisRepositoryTransition`, -`RedisRepositoryAction` and `RedisRepositoryGuard` respectively. -Generic way to update states and transition manually for redis is shown -below. This is equivalent to machine shown in +The actual repository implementations for a Redis instance are +`RedisStateRepository`, `RedisTransitionRepository`, `RedisActionRepository`, +and `RedisGuardRepository`, which are backed by the +entity classes `RedisRepositoryState`, `RedisRepositoryTransition`, +`RedisRepositoryAction`, and `RedisRepositoryGuard`, respectively. + +The next example shows the generic way to manually update states and transitions for Redis. +This is equivalent to machine shown in <>. +==== [source,java,indent=0] ---- include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetA] ---- +==== -This is equivalent to machine shown in -<>. +The following example is equivalent to machine shown in +<>: +==== [source,java,indent=0] ---- include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetB] ---- +==== [[sm-repository-config-mongodb]] ==== MongoDB -Actual _Repository_ implementations for a _MongoDB_ are -`MongoDbStateRepository`, `MongoDbTransitionRepository`, `MongoDbActionRepository` -and `MongoDbGuardRepository` which are backed by -_Entity_ classes `MongoDbRepositoryState`, `MongoDbRepositoryTransition`, -`MongoDbRepositoryAction` and `MongoDbRepositoryGuard` respectively. -Generic way to update states and transition manually for redis is shown -below. This is equivalent to machine shown in +The actual repository implementations for a MongoDB instance are +`MongoDbStateRepository`, `MongoDbTransitionRepository`, `MongoDbActionRepository`, +and `MongoDbGuardRepository`, which are backed by the +entity classes `MongoDbRepositoryState`, `MongoDbRepositoryTransition`, +`MongoDbRepositoryAction`, and `MongoDbRepositoryGuard`, respectively. + +The next example shows the generic way to manually update states and transitions for MongoDB. +This is equivalent to the machine shown in <>. +==== [source,java,indent=0] ---- include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetA] ---- +==== -This is equivalent to machine shown in +The following example is equivalent to the machine shown in <>. +==== [source,java,indent=0] ---- include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetB] ---- +==== [[sm-repository-persistence]] === Repository Persistence -Apart from storing machine configuration, shown in -<>, in an external repository it is also -possible to persist machine into repositories. -Interface `StateMachineRepository` is a central access point -interacting with machine persistence and is backed by _Entity_ class +Apart from storing machine configuration (as shown in +<>), in an external repository, you canx also +persist machines into repositories. + +The `StateMachineRepository` interface is a central access point that +interacts with machine persistence and is backed by the entity class `RepositoryStateMachine`. [[sm-repository-persistence-jpa]] ==== JPA -Actual _Repository_ implementation for a _JPA_ is -`JpaStateMachineRepository` which is backed by _Entity_ class + +The actual repository implementation for JPA is +`JpaStateMachineRepository`, which is backed by the entity class `JpaRepositoryStateMachine`. -Generic way to persist machine for jpa is shown below. +The following example shows the generic way to persist a machine for JPA: +==== [source,java,indent=0] ---- include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetD] ---- +==== [[sm-repository-persistence-redis]] ==== Redis -Actual _Repository_ implementation for a _Redis_ is -`RedisStateMachineRepository` which is backed by _Entity_ class + +The actual repository implementation for a Redis is +`RedisStateMachineRepository`, which is backed by the entity class `RedisRepositoryStateMachine`. -Generic way to persist machine for jpa is shown below. +The following example shows the generic way to persist a machine for Redis: +==== [source,java,indent=0] ---- include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetC] ---- +==== [[sm-repository-persistence-mongodb]] ==== MongoDB -Actual _Repository_ implementation for a _MongoDB_ is -`MongoDbStateMachineRepository` which is backed by _Entity_ class + +The actual repository implementation for MongoDB is +`MongoDbStateMachineRepository`, which is backed by the entity class `MongoDbRepositoryStateMachine`. -Generic way to persist machine for jpa is shown below. +The following example shows the generic way to persist a machine for MongoDB: +==== [source,java,indent=0] ---- include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetC] ---- - +==== diff --git a/docs/src/reference/asciidoc/whatsnew.adoc b/docs/src/reference/asciidoc/whatsnew.adoc index 1df9bc31..513aa412 100644 --- a/docs/src/reference/asciidoc/whatsnew.adoc +++ b/docs/src/reference/asciidoc/whatsnew.adoc @@ -2,57 +2,62 @@ = What's New == In 1.1 -_Spring Statemachine 1.1_ is focusing on security and a better -interoperability with web applications. -* Comprehensive support for _Spring Security_ is added, <> +Spring Statemachine 1.1 focuses on security and better +interoperability with web applications. It includes the following: + +* Comprehensive support for Spring Security has been added. See <>. * Context integration with `@WithStateMachine' has been greatly - enhanced, <> -* `StateContext` is now a first class citizen with how user can - interact with a State Machine, <>. -* Features around persistence has been enhanced with a build-in - support for redis, <>. -* New feature helping with persist operations, + enhanced. See <>. +* `StateContext` is now a first class citizen, letting you + interact with a State Machine. See <>. +* Features around persistence have been enhanced with built-in + support for redis. See <>. +* A new feature helps with persist operations. See <>. -* Configuration model classes are now a public API. -* New features in timer based events. -* New _Junction_ pseudostate <>. -* New _Exit Point_ and _Entry Point_ pseudostates <>. +* Configuration model classes are now in a public API. +* New features in timer-based events. +* New `Junction` pseudostate. See <>. +* New Exit Point and Entry Point pseudostates. See <>. * Configuration model verifier. -* New samples, <>, <>. -* UI modeling support using Eclipse Papyrus, <>. +* New samples. See <> and <>. +* UI modeling support using Eclipse Papyrus. See <>. == In 1.2 -_Spring Statemachine 1.2_ is focusing generic enhancements, better -_UML_ support and integrations with external config repositories. -* Support for UML submachines <> -* New _Repository_ abstraction keeping machine configuration in an - external repository <> -* New support for state actions. <> -* New transition error action concepts. <> -* New action error action concepts. <> -* Initial work for _Spring Boot_ support. <> -* Support for tracing and monitoring. <> +Spring Statemachine 1.2 focuses on generic enhancements, better +UML support, and integrations with external config repositories. +It includes the following: + +* Support for UML sub-machines. See <>. +* A new repository abstraction that keeps machine configuration in an + external repository. See <>. +* New support for state actions. See <>. +* New transition error action concepts. See <>. +* New action error concepts. See <>. +* Initial work for Spring Boot support. See <>. +* Support for tracing and monitoring. See <>. === In 1.2.8 -_Spring Statemachine 1.2.8_ contains a bit more functionality normally -not seen in a point release but these changes didn't merit a fork of -_Spring Statemachine 1.3_. -* JPA entity classes have changed table names <> -* New sample <> -* New _Entity_ classes for persistence <> - used with <> -* Transition conflict policy mentioned in +Spring Statemachine 1.2.8 contains a bit more functionality than normally +not seen in a point release, but these changes did not merit a fork of +Spring Statemachine 1.3. It includes the following: + +* JPA entity classes have changed table names. See <>. +* A new sample. See <>. +* New entity classes for persistence. See <>. +* Transition conflict policy. See <> == In 2.0 -_Spring Statemachine 2.0_ is focusing on _Spring Boot 2.x_ support. + +Spring Statemachine 2.0 focuses on Spring Boot 2.x support. === In 2.0.0 -* Format of monitoring and tracing has been changed <> -* Module `spring-statemachine-boot` has been renamed to `spring-statemachine-autoconfigure` +Spring Statemachine 2.0.0 includes the following: +* The format of monitoring and tracing has been changed. See <>. +* The `spring-statemachine-boot` module has been renamed to `spring-statemachine-autoconfigure`.