diff --git a/docs/src/reference/asciidoc/sm-accessor.adoc b/docs/src/reference/asciidoc/sm-accessor.adoc new file mode 100644 index 00000000..b2f780a4 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-accessor.adoc @@ -0,0 +1,55 @@ +[[sm-accessor]] +== 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, `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 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. + +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] +---- +==== + +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] +---- +==== + +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] +---- +==== + +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] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-actions.adoc b/docs/src/reference/asciidoc/sm-actions.adoc new file mode 100644 index 00000000..ac06afc7 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-actions.adoc @@ -0,0 +1,40 @@ +[[sm-actions]] +== Using Actions + +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] +---- +==== + +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 +your own implementation and define the appropriate implementation as a +bean. + +In the preceding example, `action3` uses a SpEL expression to send the `Events.E1` event into +a state machine. + +NOTE: `StateContext` is described in <>. + +=== SpEL Expressions with Actions + +You can also use a SpEL expression as a replacement for a +full `Action` implementation. +// TODO An example would help diff --git a/docs/src/reference/asciidoc/sm-boot.adoc b/docs/src/reference/asciidoc/sm-boot.adoc new file mode 100644 index 00000000..f0206fa2 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-boot.adoc @@ -0,0 +1,30 @@ +[[sm-boot]] +== Spring Boot Support + +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 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 + +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` properties, respectively. diff --git a/docs/src/reference/asciidoc/sm-config.adoc b/docs/src/reference/asciidoc/sm-config.adoc new file mode 100644 index 00000000..8923bce7 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-config.adoc @@ -0,0 +1,619 @@ +[[sm-config]] +== Statemachine Configuration + +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. 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 +out. + +[[statemachine-config-annotations]] +=== Using `enable` Annotations + +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. + +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. + +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 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] +---- +==== + +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 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 + +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 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 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 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 + +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 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`. + +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 + +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 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 the same `Action` instance for different +stages, but we did it here to not make too much noise in a code +snippet. + +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 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 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 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 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 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. + +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] +---- +==== + +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 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 + +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] +---- +==== + +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 + +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 + +_Pseudo state_ configuration is usually done by configuring states and +transitions. Pseudo states are automatically added to state machine as +states. + +==== Initial State + +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 + +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] +---- +==== + +==== 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 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, 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 a machine's history is +a final state. + +==== Choice State + +Choice needs to be defined in both states and transitions to work +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. + +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. + +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 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 +similarly. + +[[statemachine-config-states-junction]] +==== Junction State + +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. + +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. + +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: 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 + +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. + +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 + +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. + +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] +---- +==== + +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 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 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 + +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] +---- +==== + +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 class is simply a convenience for those times when +you want or need to do it there. + +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 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. + +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 +defaults to `CHILD`. + +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] +---- +==== + +For more about distributed states, see <>. + +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. By default, a verifier is automatically enabled and the +`DefaultStateMachineModelVerifier` implementation is used. + +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] +---- +==== + +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 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] +---- +==== + +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 people are looking for, +although it is possible. However, it is a central concept of allowing +external access to this configuration model. + +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, 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. + +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 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] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-context.adoc b/docs/src/reference/asciidoc/sm-context.adoc new file mode 100644 index 00000000..18babcbf --- /dev/null +++ b/docs/src/reference/asciidoc/sm-context.adoc @@ -0,0 +1,289 @@ +[[sm-context]] +== Context Integration + +It is a little limited to do interaction with a state machine by +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. + +The available annotations has been harmonized to enable access to the same +state machine execution points that are available from +<>. + +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] +---- +==== + +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 +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] +---- +==== + +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: The return type of these methods does not matter and is effectively +discarded. + +=== Enabling Integration + +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 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 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 the sections that desdribe the +individual annotation, later in this document. + +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. + +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] +---- +==== + +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. + +[[state-machine-transition-annotations]] +=== Transition Annotations + +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] +---- +==== + +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, 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, 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] +---- +==== + +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] +---- +==== + +=== State Annotations + +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] +---- +==== + +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, 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] +---- + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGGG] +---- +==== + +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. 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 + +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] +---- +==== + +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. 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] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-deferevents.adoc b/docs/src/reference/asciidoc/sm-deferevents.adoc new file mode 100644 index 00000000..de20bae1 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-deferevents.adoc @@ -0,0 +1,62 @@ +[[sm-deferevents]] +== Using Deferred Events + +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 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 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 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 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 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 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. diff --git a/docs/src/reference/asciidoc/sm-distributed.adoc b/docs/src/reference/asciidoc/sm-distributed.adoc new file mode 100644 index 00000000..b08c8df0 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-distributed.adoc @@ -0,0 +1,61 @@ +[[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 machine, things get a little complicated. + +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 its first official release. + +For information about generic configuration support, see +<>. For an actual usage example, see +the <> sample. + +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 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 a distributed state machine is implemented through an abstraction, +only one implementation currently exists. It is based on Zookeeper. + +The following example shows how to configure a Zookeeper-based distributed state +machine`: + +==== +[source,java,indent=0] +---- +include::samples/DocsZookeeperSampleTests.java[tags=snippetA] +---- +==== + +You can find the current technical documentation for a Zookeeker-based distributed +state machine <>. + +=== Using `ZookeeperStateMachineEnsemble` + +`ZookeeperStateMachineEnsemble` itself needs two mandatory settings, +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, 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, 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. diff --git a/docs/src/reference/asciidoc/sm-error-handling.adoc b/docs/src/reference/asciidoc/sm-error-handling.adoc new file mode 100644 index 00000000..85b6c144 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-error-handling.adoc @@ -0,0 +1,52 @@ +[[sm-error-handling]] +== State Machine Error Handling + +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, 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, the following example shows a simple listener: + +==== +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippet2] +---- +==== + +The following example shows a generic `ApplicationListener` checking `StateMachineEvent`: + +==== +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippet3] +---- +==== + +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. See <>. diff --git a/docs/src/reference/asciidoc/sm-extendedstate.adoc b/docs/src/reference/asciidoc/sm-extendedstate.adoc new file mode 100644 index 00000000..9335448b --- /dev/null +++ b/docs/src/reference/asciidoc/sm-extendedstate.adoc @@ -0,0 +1,50 @@ +[[sm-extendedstate]] +== Using Extended State + +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. + +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 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] +---- +==== + +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] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-factories.adoc b/docs/src/reference/asciidoc/sm-factories.adoc new file mode 100644 index 00000000..01a3360b --- /dev/null +++ b/docs/src/reference/asciidoc/sm-factories.adoc @@ -0,0 +1,97 @@ +[[sm-factories]] +== State Machine Factories + +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 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 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 + +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 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] +---- +==== + +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` +you can use dynamically through a builder. + +NOTE: Currently, the `builder.configureStates()`, `builder.configureTransitions()`, +and `builder.configureConfiguration()` interface methods cannot be +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] +---- +==== + +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. diff --git a/docs/src/reference/asciidoc/sm-guards.adoc b/docs/src/reference/asciidoc/sm-guards.adoc new file mode 100644 index 00000000..4b542f83 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-guards.adoc @@ -0,0 +1,37 @@ +[[sm-guards]] +== Using Guards + +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 +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 <>. + +=== SpEL Expressions with Guards + +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 diff --git a/docs/src/reference/asciidoc/sm-interceptor.adoc b/docs/src/reference/asciidoc/sm-interceptor.adoc new file mode 100644 index 00000000..046596e5 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-interceptor.adoc @@ -0,0 +1,30 @@ +[[sm-interceptor]] +== Using `StateMachineInterceptor` + +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: + +==== +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetZH] +---- +==== + +NOTE: For more about the error handling shown in preceding example, see +<>. diff --git a/docs/src/reference/asciidoc/sm-listeners.adoc b/docs/src/reference/asciidoc/sm-listeners.adoc new file mode 100644 index 00000000..7396b727 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-listeners.adoc @@ -0,0 +1,101 @@ +[[sm-listeners]] +== Listening to State Machine Events + +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`, +`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 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] +---- +==== + +=== 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 the preceding example, we created our own listener class +(`StateMachineEventListener`) that extends +`StateMachineListenerAdapter`. + +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 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 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] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-machineid.adoc b/docs/src/reference/asciidoc/sm-machineid.adoc new file mode 100644 index 00000000..c2258f94 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-machineid.adoc @@ -0,0 +1,80 @@ +[[sm-machineid]] +== State Machine ID + +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 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 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`. + +=== 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: 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] +---- +==== + +=== Using `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` (see <>) uses +`machineId` to support different configurations in a persistent +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, it defaults to repository configuration (in a backing-persistent model) +without a known machine id. + +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. diff --git a/docs/src/reference/asciidoc/sm-monitoring.adoc b/docs/src/reference/asciidoc/sm-monitoring.adoc new file mode 100644 index 00000000..0d094c56 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-monitoring.adoc @@ -0,0 +1,25 @@ +[[sm-monitoring]] +== 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 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: See the <> sample for detailed usage. diff --git a/docs/src/reference/asciidoc/sm-papyrus.adoc b/docs/src/reference/asciidoc/sm-papyrus.adoc new file mode 100644 index 00000000..862bb57a --- /dev/null +++ b/docs/src/reference/asciidoc/sm-papyrus.adoc @@ -0,0 +1,357 @@ +[[sm-papyrus]] +== Eclipse Modeling Support + +Defining a state machine configuration with UI modeling is supported +through the Eclipse Papyrus framework. + +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 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 the scenes, a raw UML file would look like the following example: + +==== +[source,xml,indent=0] +---- +include::samples/simple-machine.uml[] +---- +==== + +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` + +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 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 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 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 <>. + +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]] +==== Using `StateMachineComponentResolver` + +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 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 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 StateMachine Diagram, as follows: + +image::images/papyrus-gs-3.png[scaledwidth="100%"] + +You end up with an empty state machine. + +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. + +=== 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 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 the preceding image, we added a second state (`S2`) and added a transition between +S1 and S2 (indicating that we have two states). + +=== 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%"] + +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 defined a `SignalEvent`, you can use it to associate +a trigger with a transition. For more about that, see +<>. + +==== Deferring an Event + +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]] +=== 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, as the following image shows: + +image::images/papyrus-gs-8.png[scaledwidth="100%"] + +This gives you something like the arrangement shown in the following image: + +image::images/papyrus-gs-9.png[scaledwidth="100%"] + +TIP: If you omit SignalEvent for a transition, it becomes an +anonymous transition. + +=== 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. + +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 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 the user can pick one of these timed events instead of a +signal event for a particular transition. + +[[sm-papyrus-choice]] +=== Defining a Choice + +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 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 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. + +=== Defining a Junction +See <>. + +=== 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 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 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. + +=== 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 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 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 History + +In cases where a Transition terminates on a history when +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 must define a transition +into this default state. This is the transition from `SH` to +`S22`. + +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%"] + +=== 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%"] + +=== Defining Actions + +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. + +=== 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]] +=== 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]] +=== 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 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. + +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 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 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, 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%"] diff --git a/docs/src/reference/asciidoc/sm-persist.adoc b/docs/src/reference/asciidoc/sm-persist.adoc new file mode 100644 index 00000000..fa8ad336 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-persist.adoc @@ -0,0 +1,141 @@ +[[sm-persist]] +== 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. + +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 (see <>) and one sample +(see <>) that provide more info about +persisting states. + +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. + +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, +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` + +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 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: The <> sample shows +how you can persist parallel regions. + +[[sm-persist-statemachinepersister]] +=== Using `StateMachinePersister` + +Building a `StateMachineContext` and then restoring a state machine +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`. + +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 are using a `StateMachinePersist` object, we can create an in-memory +implementation. + +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 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 + +`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. + +TIP: See the <> sample for detailed usage. + +`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` + +`StateMachineRuntimePersister` is a simple extension to +`StateMachinePersist` that adds an interface-level method to get +`StateMachineInterceptor` associated with it. This interceptor is then +required to persist a machine during state changes without needing to +stop and start a machine. + +Currently, there are implementations for this interface for the +supported Spring Data Repositories. These implementations are +`JpaPersistingStateMachineInterceptor`, `MongoDbPersistingStateMachineInterceptor`, +and `RedisPersistingStateMachineInterceptor`. + +TIP: See the <> sample for detailed usage. diff --git a/docs/src/reference/asciidoc/sm-repository.adoc b/docs/src/reference/asciidoc/sm-repository.adoc new file mode 100644 index 00000000..7000d642 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-repository.adoc @@ -0,0 +1,253 @@ +[[sm-repository]] +== Repository Support + +This section contains documentation related to using 'Spring Data +Repositories' in Spring Statemachine. + +[[sm-repository-config]] +=== Repository Configuration + +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. + +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 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 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"] + +[[image-sm-repository-simplesubmachine]] +image::images/sm-repository-simplesubmachine.png[scaledwidth="100%", title="SimpleSubMachine"] + +[[image-sm-repository-showcasemachine]] +image::images/sm-repository-showcasemachine.png[scaledwidth="100%", title="ShowcaseMachine"] + +[[sm-repository-config-jpa]] +==== JPA + +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 '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 +<>): + +==== +[source,java,indent=0] +---- +include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetA] +---- +==== + +The following example is also equivalent to the machine shown in +<>. + +==== +[source,java,indent=0] +---- +include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetB] +---- +==== + +First, you must access all repositories. +The following example shows how to do so: + +==== +[source,java,indent=0] +---- +include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC1] +---- +==== + +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] +---- +==== + +Third, you must create states. +The following example shows how to do so: + +==== +[source,java,indent=0] +---- +include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC3] +---- +==== + +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] +---- +==== + +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 + +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] +---- +==== + +The following example is equivalent to machine shown in +<>: + +==== +[source,java,indent=0] +---- +include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetB] +---- +==== + +[[sm-repository-config-mongodb]] +==== MongoDB + +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] +---- +==== + +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 (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 + +The actual repository implementation for JPA is +`JpaStateMachineRepository`, which is backed by the entity class +`JpaRepositoryStateMachine`. + +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 + +The actual repository implementation for a Redis is +`RedisStateMachineRepository`, which is backed by the entity class +`RedisRepositoryStateMachine`. + +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 + +The actual repository implementation for MongoDB is +`MongoDbStateMachineRepository`, which is backed by the entity class +`MongoDbRepositoryStateMachine`. + +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/sm-scopes.adoc b/docs/src/reference/asciidoc/sm-scopes.adoc new file mode 100644 index 00000000..78a24311 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-scopes.adoc @@ -0,0 +1,48 @@ +[[sm-scopes]] +== Using Scopes + +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] +---- + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests2.java[tags=snippetC] +---- +==== + +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 careful planning, +mostly because it is a relatively heavy component. + +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. diff --git a/docs/src/reference/asciidoc/sm-security.adoc b/docs/src/reference/asciidoc/sm-security.adoc new file mode 100644 index 00000000..9fa3b143 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-security.adoc @@ -0,0 +1,286 @@ +[[sm-security]] +== State Machine Security + +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 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]). + +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. + +You can find more detailed information in <>. + +TIP: For a complete example, see the <> sample. + +=== Configuring Security + +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 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 by a `SecurityConfigurer`. +The following example shows how to enable event security: + +==== +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests3.java[tags=snippetA] +---- +==== + +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 + +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 override any +globally set security. The following example shows how to do so: + +==== +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests3.java[tags=snippetB] +---- +==== + +For more about using attributes and expressions, see <>. + +=== Securing Actions + +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 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, 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 +harder to use. + +==== Generic Attribute Usage + +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 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 +|=== +| Expression | Description + +| `hasRole([role])` +| Returns `true` if the current principal has the specified role. By +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 each +supplied role does not start with `ROLE_`, it is added. You can customize this +by modifying the `defaultRolePrefix` on +`DefaultWebSecurityExpressionHandler`. + +| `hasAuthority([authority])` +| Returns `true` if the current principal has the specified authority. + +| `hasAnyAuthority([authority1,authority2])` +| Returns `true` if the current principal has any of the supplied +roles (given as a comma-separated list of strings). + +| `principal` +| 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`. + +| `permitAll` +| Always evaluates to `true`. + +| `denyAll` +| Always evaluates to `false`. + +| `isAnonymous()` +| Returns `true` if the current principal is an anonymous user. + +| `isRememberMe()` +| Returns `true` if the current principal is a remember-me user. + +| `isAuthenticated()` +| Returns `true` if the user is not anonymous. + +| `isFullyAuthenticated()` +| 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')`. + +| `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')`. +|=== + +==== Event Attributes +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 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 +|=== +| Expression | Description + +| `hasEvent(Object event)` +| Returns `true` if the event matches given event. + +|=== + +==== Transition Attributes + +When matching transition sources and targets, you can use the +`TRANSITION_SOURCE_` and `TRANSITION_TARGET_` prefixes respectively. + +==== Transition Expressions + +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. +`TransitionSecurityExpressionRoot` has two methods, which the following +table describes: + +.Transition expressions +|=== +| Expression | Description + +| `hasSource(Object source)` +| Returns `true` if the transition source matches given source. + +| `hasTarget(Object target)` +| Returns `true` if the transition target matches given target. + +|=== + +[[sm-security-details]] +=== Understanding Security + +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. + +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. + +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` 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 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. + +By default, for events, voters (`EventExpressionVoter`, `EventVoter`, and +`RoleVoter`) are added into an `AccessDecisionManager`. + +By default, for transitions, voters (`TransitionExpressionVoter`, +`TransitionVoter`, and `RoleVoter`) are added into an `AccessDecisionManager`. diff --git a/docs/src/reference/asciidoc/sm-service.adoc b/docs/src/reference/asciidoc/sm-service.adoc new file mode 100644 index 00000000..18c10f51 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-service.adoc @@ -0,0 +1,14 @@ +[[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 +(`StateMachineService`) exists. + +[[sm-service-statemachineservice]] +=== 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`. diff --git a/docs/src/reference/asciidoc/sm-statecontext.adoc b/docs/src/reference/asciidoc/sm-statecontext.adoc new file mode 100644 index 00000000..6331e204 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-statecontext.adoc @@ -0,0 +1,40 @@ +[[sm-statecontext]] +== Using `StateContext` + +{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. + +You can use `StateContext` to get access to the following: + +* 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, such as +`Action` and `Guard`. + +[[sm-statecontext-stage]] +=== Stages + +{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`. These states may look familiar, as +they match how you can interact with listeners (as described in +<>). diff --git a/docs/src/reference/asciidoc/sm-test.adoc b/docs/src/reference/asciidoc/sm-test.adoc new file mode 100644 index 00000000..58483232 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-test.adoc @@ -0,0 +1,51 @@ +[[sm-test]] +== Testing Support + +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` 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. + +The following example uses `StateMachineBuilder` to build a state machine: + +==== +[source,java,indent=0] +---- +include::samples/DocsTestSampleTests.java[tags=snippetB] +---- +==== + +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. 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. + +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 results are documented in the Javadoc for +{sm-statemachinetestplanbuilder-statemachinetestplanstepbuilder}[`StateMachineTestPlanStepBuilder`]. diff --git a/docs/src/reference/asciidoc/sm-triggers.adoc b/docs/src/reference/asciidoc/sm-triggers.adoc new file mode 100644 index 00000000..4d5f1e54 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-triggers.adoc @@ -0,0 +1,81 @@ +[[sm-triggers]] +== Triggering Transitions + +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. 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] +---- +==== + +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`. + +=== 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 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] +---- +==== + +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. The interesting parts +for working with `TimerTrigger` are when we define +internal transitions for source states `S2` and `S3`. + +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 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 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 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 +exactly once when state is entered. diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index c2284cb1..305d155e 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -54,2925 +54,50 @@ It includes the following topics: * <> describes the state machine repository config support. -[[sm-config]] -== Statemachine Configuration +include::sm-config.adoc[] -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. +include::sm-machineid.adoc[] -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 -out. +include::sm-factories.adoc[] -[[statemachine-config-annotations]] -=== Using `enable` Annotations +include::sm-deferevents.adoc[] -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. +include::sm-scopes.adoc[] -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. +include::sm-actions.adoc[] -You can use `@EnableStateMachineFactory` when you need a configuration to create an -instance of a `StateMachineFactory`. +include::sm-guards.adoc[] -NOTE: Usage examples of these are shown in below sections. +include::sm-extendedstate.adoc[] -[[statemachine-config-states]] -=== Configuring States +include::sm-statecontext.adoc[] -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. +include::sm-triggers.adoc[] -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippetAA] ----- -==== +include::sm-listeners.adoc[] -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. +include::sm-context.adoc[] -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippetAB] ----- -==== +include::sm-accessor.adoc[] -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. +include::sm-interceptor.adoc[] -=== Configuring Hierarchical States +include::sm-security.adoc[] -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: +include::sm-error-handling.adoc[] -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippetB] ----- -==== +include::sm-service.adoc[] -=== Configuring Regions +include::sm-persist.adoc[] -There are no special configuration methods to mark a collection of -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: +include::sm-boot.adoc[] -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippetP] ----- -==== +include::sm-monitoring.adoc[] -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: +include::sm-distributed.adoc[] -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippetPP] ----- -==== +include::sm-test.adoc[] -=== Configuring Transitions +include::sm-papyrus.adoc[] -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 - -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 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`. - -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 - -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 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 the same `Action` instance for different -stages, but we did it here to not make too much noise in a code -snippet. - -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 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 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 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 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 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. - -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] ----- -==== - -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 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 - -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] ----- -==== - -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 - -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 - -_Pseudo state_ configuration is usually done by configuring states and -transitions. Pseudo states are automatically added to state machine as -states. - -==== Initial State - -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 - -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] ----- -==== - -==== 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 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, 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 a machine's history is -a final state. - -==== Choice State - -Choice needs to be defined in both states and transitions to work -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. - -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. - -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 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 -similarly. - -[[statemachine-config-states-junction]] -==== Junction State - -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. - -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. - -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: 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 - -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. - -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 - -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. - -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] ----- -==== - -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 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 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 - -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] ----- -==== - -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 class is simply a convenience for those times when -you want or need to do it there. - -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 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. - -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 -defaults to `CHILD`. - -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] ----- -==== - -For more about distributed states, see <>. - -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. By default, a verifier is automatically enabled and the -`DefaultStateMachineModelVerifier` implementation is used. - -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] ----- -==== - -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 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] ----- -==== - -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 people are looking for, -although it is possible. However, it is a central concept of allowing -external access to this configuration model. - -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, 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. - -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 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 as a -parameter in methods. This section takes a closer look at how -`machineId` relates to normal machine operation and instantiation. - -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 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`. - -=== 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: 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] ----- -==== - -=== Using `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` (see <>) uses -`machineId` to support different configurations in a persistent -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, it defaults to repository configuration (in a backing-persistent model) -without a known machine id. - -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 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 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 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 - -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 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] ----- -==== - -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` -you can use dynamically through a builder. - -NOTE: Currently, the `builder.configureStates()`, `builder.configureTransitions()`, -and `builder.configureConfiguration()` interface methods cannot be -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] ----- -==== - -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 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 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 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 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 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 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 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 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] ----- - -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests2.java[tags=snippetC] ----- -==== - -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 careful planning, -mostly because it is a relatively heavy component. - -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 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] ----- -==== - -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 -your own implementation and define the appropriate implementation as a -bean. - -In the preceding example, `action3` uses a SpEL expression to send the `Events.E1` event into -a state machine. - -NOTE: `StateContext` is described in <>. - -=== 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 - -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 -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 <>. - -=== SpEL Expressions with Guards - -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 - -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. - -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 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] ----- -==== - -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 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. - -You can use `StateContext` to get access to the following: - -* 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, such as -`Action` and `Guard`. - -[[sm-statecontext-stage]] -=== Stages - -{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`. These states may look familiar, as -they match how you can interact with listeners (as described in -<>). - -[[sm-triggers]] -== Triggering Transitions - -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. 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] ----- -==== - -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`. - -=== 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 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] ----- -==== - -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. The interesting parts -for working with `TimerTrigger` are when we define -internal transitions for source states `S2` and `S3`. - -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 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 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 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 -exactly once when state is entered. - -[[sm-listeners]] -== Listening to State Machine Events - -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`, -`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 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] ----- -==== - -=== 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 the preceding example, we created our own listener class -(`StateMachineEventListener`) that extends -`StateMachineListenerAdapter`. - -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 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 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 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. - -The available annotations has been harmonized to enable access to the same -state machine execution points that are available from -<>. - -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] ----- -==== - -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 -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] ----- -==== - -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: The return type of these methods does not matter and is effectively -discarded. - -=== Enabling Integration - -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 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 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 the sections that desdribe the -individual annotation, later in this document. - -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. - -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] ----- -==== - -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. - -[[state-machine-transition-annotations]] -=== Transition Annotations - -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] ----- -==== - -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, 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, 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] ----- -==== - -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] ----- -==== - -=== State Annotations - -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] ----- -==== - -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, 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] ----- - -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests4.java[tags=snippetGGGG] ----- -==== - -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. 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 - -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] ----- -==== - -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. 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]] -== 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, `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 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. - -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] ----- -==== - -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] ----- -==== - -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] ----- -==== - -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]] -== Using `StateMachineInterceptor` - -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: - -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippetZH] ----- -==== - -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 -{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 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]). - -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. - -You can find more detailed information in <>. - -TIP: For a complete example, see the <> sample. - -=== Configuring Security - -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 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 by a `SecurityConfigurer`. -The following example shows how to enable event security: - -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests3.java[tags=snippetA] ----- -==== - -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 - -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 override any -globally set security. The following example shows how to do so: - -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests3.java[tags=snippetB] ----- -==== - -For more about using attributes and expressions, see <>. - -=== Securing Actions - -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 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, 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 -harder to use. - -==== Generic Attribute Usage - -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 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 -|=== -| Expression | Description - -| `hasRole([role])` -| Returns `true` if the current principal has the specified role. By -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 each -supplied role does not start with `ROLE_`, it is added. You can customize this -by modifying the `defaultRolePrefix` on -`DefaultWebSecurityExpressionHandler`. - -| `hasAuthority([authority])` -| Returns `true` if the current principal has the specified authority. - -| `hasAnyAuthority([authority1,authority2])` -| Returns `true` if the current principal has any of the supplied -roles (given as a comma-separated list of strings). - -| `principal` -| 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`. - -| `permitAll` -| Always evaluates to `true`. - -| `denyAll` -| Always evaluates to `false`. - -| `isAnonymous()` -| Returns `true` if the current principal is an anonymous user. - -| `isRememberMe()` -| Returns `true` if the current principal is a remember-me user. - -| `isAuthenticated()` -| Returns `true` if the user is not anonymous. - -| `isFullyAuthenticated()` -| 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')`. - -| `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')`. -|=== - -==== Event Attributes -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 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 -|=== -| Expression | Description - -| `hasEvent(Object event)` -| Returns `true` if the event matches given event. - -|=== - -==== Transition Attributes - -When matching transition sources and targets, you can use the -`TRANSITION_SOURCE_` and `TRANSITION_TARGET_` prefixes respectively. - -==== Transition Expressions - -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. -`TransitionSecurityExpressionRoot` has two methods, which the following -table describes: - -.Transition expressions -|=== -| Expression | Description - -| `hasSource(Object source)` -| Returns `true` if the transition source matches given source. - -| `hasTarget(Object target)` -| Returns `true` if the transition target matches given target. - -|=== - -[[sm-security-details]] -=== Understanding Security - -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. - -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. - -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` 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 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. - -By default, for events, voters (`EventExpressionVoter`, `EventVoter`, and -`RoleVoter`) are added into an `AccessDecisionManager`. - -By default, for transitions, voters (`TransitionExpressionVoter`, -`TransitionVoter`, and `RoleVoter`) are added into an `AccessDecisionManager`. - -[[sm-error-handling]] -== State Machine Error Handling - -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, 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, the following example shows a simple listener: - -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippet2] ----- -==== - -The following example shows a generic `ApplicationListener` checking `StateMachineEvent`: - -==== -[source,java,indent=0] ----- -include::samples/DocsConfigurationSampleTests.java[tags=snippet3] ----- -==== - -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. 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 -(`StateMachineService`) exists. - -[[sm-service-statemachineservice]] -=== 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 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. - -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 (see <>) and one sample -(see <>) that provide more info about -persisting states. - -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. - -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, -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` - -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 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: The <> sample shows -how you can persist parallel regions. - -[[sm-persist-statemachinepersister]] -=== Using `StateMachinePersister` - -Building a `StateMachineContext` and then restoring a state machine -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`. - -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 are using a `StateMachinePersist` object, we can create an in-memory -implementation. - -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 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 - -`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. - -TIP: See the <> sample for detailed usage. - -`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` - -`StateMachineRuntimePersister` is a simple extension to -`StateMachinePersist` that adds an interface-level method to get -`StateMachineInterceptor` associated with it. This interceptor is then -required to persist a machine during state changes without needing to -stop and start a machine. - -Currently, there are implementations for this interface for the -supported Spring Data Repositories. These implementations are -`JpaPersistingStateMachineInterceptor`, `MongoDbPersistingStateMachineInterceptor`, -and `RedisPersistingStateMachineInterceptor`. - -TIP: See the <> sample for detailed usage. - -[[sm-boot]] -== Spring Boot Support - -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 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 - -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` properties, respectively. - -[[sm-monitoring]] -== 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 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: 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 machine, things get a little complicated. - -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 its first official release. - -For information about generic configuration support, see -<>. For an actual usage example, see -the <> sample. - -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 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 a distributed state machine is implemented through an abstraction, -only one implementation currently exists. It is based on Zookeeper. - -The following example shows how to configure a Zookeeper-based distributed state -machine`: - -==== -[source,java,indent=0] ----- -include::samples/DocsZookeeperSampleTests.java[tags=snippetA] ----- -==== - -You can find the current technical documentation for a Zookeeker-based distributed -state machine <>. - -=== Using `ZookeeperStateMachineEnsemble` - -`ZookeeperStateMachineEnsemble` itself needs two mandatory settings, -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, 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, 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 ease testing of state -machine instances. These are used in the framework itself but are also -very useful for end users. - -`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. - -The following example uses `StateMachineBuilder` to build a state machine: - -==== -[source,java,indent=0] ----- -include::samples/DocsTestSampleTests.java[tags=snippetB] ----- -==== - -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. 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. - -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 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 -through the Eclipse Papyrus framework. - -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 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 the scenes, a raw UML file would look like the following example: - -==== -[source,xml,indent=0] ----- -include::samples/simple-machine.uml[] ----- -==== - -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` - -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 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 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 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 <>. - -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]] -==== Using `StateMachineComponentResolver` - -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 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 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 StateMachine Diagram, as follows: - -image::images/papyrus-gs-3.png[scaledwidth="100%"] - -You end up with an empty state machine. - -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. - -=== 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 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 the preceding image, we added a second state (`S2`) and added a transition between -S1 and S2 (indicating that we have two states). - -=== 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%"] - -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 defined a `SignalEvent`, you can use it to associate -a trigger with a transition. For more about that, see -<>. - -==== Deferring an Event - -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]] -=== 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, as the following image shows: - -image::images/papyrus-gs-8.png[scaledwidth="100%"] - -This gives you something like the arrangement shown in the following image: - -image::images/papyrus-gs-9.png[scaledwidth="100%"] - -TIP: If you omit SignalEvent for a transition, it becomes an -anonymous transition. - -=== 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. - -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 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 the user can pick one of these timed events instead of a -signal event for a particular transition. - -[[sm-papyrus-choice]] -=== Defining a Choice - -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 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 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. - -=== Defining a Junction -See <>. - -=== 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 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 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. - -=== 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 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 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 History - -In cases where a Transition terminates on a history when -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 must define a transition -into this default state. This is the transition from `SH` to -`S22`. - -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%"] - -=== 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%"] - -=== Defining Actions - -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. - -=== 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]] -=== 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]] -=== 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 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. - -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 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 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, 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' in Spring Statemachine. - -[[sm-repository-config]] -=== Repository Configuration - -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. - -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 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 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"] - -[[image-sm-repository-simplesubmachine]] -image::images/sm-repository-simplesubmachine.png[scaledwidth="100%", title="SimpleSubMachine"] - -[[image-sm-repository-showcasemachine]] -image::images/sm-repository-showcasemachine.png[scaledwidth="100%", title="ShowcaseMachine"] - -[[sm-repository-config-jpa]] -==== JPA - -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 '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 -<>): - -==== -[source,java,indent=0] ----- -include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetA] ----- -==== - -The following example is also equivalent to the machine shown in -<>. - -==== -[source,java,indent=0] ----- -include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetB] ----- -==== - -First, you must access all repositories. -The following example shows how to do so: - -==== -[source,java,indent=0] ----- -include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC1] ----- -==== - -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] ----- -==== - -Third, you must create states. -The following example shows how to do so: - -==== -[source,java,indent=0] ----- -include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetC3] ----- -==== - -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] ----- -==== - -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 - -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] ----- -==== - -The following example is equivalent to machine shown in -<>: - -==== -[source,java,indent=0] ----- -include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetB] ----- -==== - -[[sm-repository-config-mongodb]] -==== MongoDB - -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] ----- -==== - -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 (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 - -The actual repository implementation for JPA is -`JpaStateMachineRepository`, which is backed by the entity class -`JpaRepositoryStateMachine`. - -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 - -The actual repository implementation for a Redis is -`RedisStateMachineRepository`, which is backed by the entity class -`RedisRepositoryStateMachine`. - -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 - -The actual repository implementation for MongoDB is -`MongoDbStateMachineRepository`, which is backed by the entity class -`MongoDbRepositoryStateMachine`. - -The following example shows the generic way to persist a machine for MongoDB: - -==== -[source,java,indent=0] ----- -include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetC] ----- -==== +include::sm-repository.adoc[]