Split main docs for reactive changes

- For main sm.adoc, split every chapter into its own
  file to easy further reactive docs changes.
- Relates #742
This commit is contained in:
Janne Valkealahti
2019-05-11 17:33:14 +01:00
parent 7f1fa456aa
commit 37b1b37a70
25 changed files with 2923 additions and 2899 deletions

View File

@@ -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]
----
====

View File

@@ -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 <<sm-statecontext>>.
=== SpEL Expressions with Actions
You can also use a SpEL expression as a replacement for a
full `Action` implementation.
// TODO An example would help

View File

@@ -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
<<statemachine-examples-monitoring>> 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 <<sm-repository>>.
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.

View File

@@ -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 <<sm-distributed>>.
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 <<devdocs-configmodel>>.
NOTE: The `withSecurity`, `withMonitoring` and `withPersistence` configuration methods
are documented in <<sm-security>>, <<sm-monitoring>>, and
<<sm-persist-statemachineruntimepersister>>, 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
<<sm-papyrus>>. You can find more generic info about custom model integration
in <<devdocs>>.
[[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]
----
====

View File

@@ -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
<<sm-listeners>>.
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
<<sm-statecontext>>.
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 <<state-machine-transition-annotations>>, 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]
----
====

View File

@@ -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 states 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.

View File

@@ -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
<<statemachine-config-commonsettings>>. For an actual usage example, see
the <<statemachine-examples-zookeeper>> 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 <<appendices-zookeeper,in the appendix>>.
=== 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.

View File

@@ -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
<<sm-listeners>>.
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 <<statemachine-config-transition-actions-errorhandling>>.

View File

@@ -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 <<sm-listeners>>,
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]
----
====

View File

@@ -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.

View File

@@ -0,0 +1,37 @@
[[sm-guards]]
== Using Guards
As shown in <<statemachine\-config\-thingstoremember>>, 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 <<sm-statecontext>>.
=== 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

View File

@@ -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 (<<statemachine-recipes-persist>>) and one sample
(<<statemachine-examples-persist>>) 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-error-handling>>.

View File

@@ -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
<<sm-statecontext>>.
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]
----
====

View File

@@ -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 <<state-machine-via-builder>>) 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<S, E> build(String machineId)` which a `StateMachineModelFactory`
implementation may choose to use.
`RepositoryStateMachineModelFactory` (see <<sm-repository>>) 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<T>
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.

View File

@@ -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 <<statemachine-examples-monitoring>> sample for detailed usage.

View File

@@ -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 <<sm-papyrus-statemachinecomponentresolver>>.
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
<<sm-papyrus-transitions>>.
==== 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 <<sm-papyrus-choice>>.
=== 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 <<sm-papyrus-beanref>>.
==== Using an Initial Action
An initial action (as shown in <<statemachine-config-actions>>) 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>>.
[[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%"]

View File

@@ -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 <<statemachine-recipes-persist>>) and one sample
(see <<statemachine-examples-persist>>) 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
<<sm-interceptor>> 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 <<statemachine-examples-datajpamultipersist>> 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 <<statemachine-examples-eventservice>> 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 <<statemachine-examples-datapersist>> sample for detailed usage.

View File

@@ -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
<<image-sm-repository-simplemachine>>):
====
[source,java,indent=0]
----
include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetA]
----
====
The following example is also equivalent to the machine shown in
<<image-sm-repository-simplesubmachine>>.
====
[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
<<statemachine-examples-datajpa, here>>. 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
<<image-sm-repository-simplemachine>>.
====
[source,java,indent=0]
----
include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetA]
----
====
The following example is equivalent to machine shown in
<<image-sm-repository-simplesubmachine>>:
====
[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
<<image-sm-repository-simplemachine>>.
====
[source,java,indent=0]
----
include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetA]
----
====
The following example is equivalent to the machine shown in
<<image-sm-repository-simplesubmachine>>.
====
[source,java,indent=0]
----
include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetB]
----
====
[[sm-repository-persistence]]
=== Repository Persistence
Apart from storing machine configuration (as shown in
<<sm-repository-config>>), 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]
----
====

View File

@@ -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 <<statemachine-examples-scope>> 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.

View File

@@ -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 <<sm-security-details>>.
TIP: For a complete example, see the <<statemachine-examples-security>> 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 <<sm-security-attributes-expressions>>.
=== 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 <<sm-security-attributes-expressions>>.
=== 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
<<sm-interceptor,`StateMachineInterceptor`>>, 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`.

View File

@@ -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`.

View File

@@ -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 <<sm-statecontext-stage>>.
`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-listeners>>).

View File

@@ -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`].

View File

@@ -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<E> 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.

File diff suppressed because it is too large Load Diff