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:
55
docs/src/reference/asciidoc/sm-accessor.adoc
Normal file
55
docs/src/reference/asciidoc/sm-accessor.adoc
Normal 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]
|
||||
----
|
||||
====
|
||||
40
docs/src/reference/asciidoc/sm-actions.adoc
Normal file
40
docs/src/reference/asciidoc/sm-actions.adoc
Normal 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
|
||||
30
docs/src/reference/asciidoc/sm-boot.adoc
Normal file
30
docs/src/reference/asciidoc/sm-boot.adoc
Normal 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.
|
||||
619
docs/src/reference/asciidoc/sm-config.adoc
Normal file
619
docs/src/reference/asciidoc/sm-config.adoc
Normal 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]
|
||||
----
|
||||
====
|
||||
289
docs/src/reference/asciidoc/sm-context.adoc
Normal file
289
docs/src/reference/asciidoc/sm-context.adoc
Normal 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]
|
||||
----
|
||||
====
|
||||
62
docs/src/reference/asciidoc/sm-deferevents.adoc
Normal file
62
docs/src/reference/asciidoc/sm-deferevents.adoc
Normal 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 state’s deferred event list occurs, the event is saved
|
||||
(deferred) for future processing until a state is entered that does not list
|
||||
the event in its deferred event list. When such a state is entered, the
|
||||
state machine automatically recalls any saved events that are no longer
|
||||
deferred and then either consumes or discards these events. It is possible
|
||||
for a superstate to have a transition defined on an event that is deferred
|
||||
by a substate. Following same hierarchical state machines concepts, the substate
|
||||
takes precedence over the superstate, the event is deferred, and the
|
||||
transition for the superstate is not run. With orthogonal regions,
|
||||
where one orthogonal region defers an event and another accepts the event, the
|
||||
accept takes precedence and the event is consumed and not deferred.
|
||||
|
||||
The most obvious use case for event deferring is when an event causes
|
||||
a transition into a particular state and the state machine is then returned back
|
||||
to its original state where a second event should cause the same
|
||||
transition. The following example shows this situation:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests2.java[tags=snippetE]
|
||||
----
|
||||
====
|
||||
|
||||
In the preceding example, the state machine has a state of `READY`, which indicates that the machine is
|
||||
ready to process events that would take it into a `DEPLOY` state, where the
|
||||
actual deployment would happen. After a deploy action has been run, the machine
|
||||
is returned back to the `READY` state. Sending multiple events in a
|
||||
`READY` state does not cause any trouble if the machine is using synchronous executors,
|
||||
because event sending would block between event calls. However, if the executor uses
|
||||
threads, other events may get lost, because the machine is no longer in a state where
|
||||
events can be processed. Thus, deferring some of these events lets the machine
|
||||
preserve them. The following example shows how to configure such an arrangement:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests2.java[tags=snippetF]
|
||||
----
|
||||
====
|
||||
|
||||
In the preceding example, the state machine uses nested states instead of a flat
|
||||
state model, so the `DEPLOY` event can be deferred directly in a substate.
|
||||
It also shows the concept of deferring the `DONE` event in a
|
||||
sub-state that would then override the anonymous transition between
|
||||
the `DEPLOY` and `DONE` states if the state machine happens to be in a
|
||||
`DEPLOYPREPARE` state when the `DONE` event is dispatched. In the
|
||||
`DEPLOYEXECUTE` state when the `DONE` event is not deferred, this event would
|
||||
be handled in a super state.
|
||||
61
docs/src/reference/asciidoc/sm-distributed.adoc
Normal file
61
docs/src/reference/asciidoc/sm-distributed.adoc
Normal 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.
|
||||
52
docs/src/reference/asciidoc/sm-error-handling.adoc
Normal file
52
docs/src/reference/asciidoc/sm-error-handling.adoc
Normal 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>>.
|
||||
50
docs/src/reference/asciidoc/sm-extendedstate.adoc
Normal file
50
docs/src/reference/asciidoc/sm-extendedstate.adoc
Normal 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]
|
||||
----
|
||||
====
|
||||
97
docs/src/reference/asciidoc/sm-factories.adoc
Normal file
97
docs/src/reference/asciidoc/sm-factories.adoc
Normal 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.
|
||||
37
docs/src/reference/asciidoc/sm-guards.adoc
Normal file
37
docs/src/reference/asciidoc/sm-guards.adoc
Normal 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
|
||||
30
docs/src/reference/asciidoc/sm-interceptor.adoc
Normal file
30
docs/src/reference/asciidoc/sm-interceptor.adoc
Normal 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>>.
|
||||
101
docs/src/reference/asciidoc/sm-listeners.adoc
Normal file
101
docs/src/reference/asciidoc/sm-listeners.adoc
Normal 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]
|
||||
----
|
||||
====
|
||||
80
docs/src/reference/asciidoc/sm-machineid.adoc
Normal file
80
docs/src/reference/asciidoc/sm-machineid.adoc
Normal 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.
|
||||
25
docs/src/reference/asciidoc/sm-monitoring.adoc
Normal file
25
docs/src/reference/asciidoc/sm-monitoring.adoc
Normal 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.
|
||||
357
docs/src/reference/asciidoc/sm-papyrus.adoc
Normal file
357
docs/src/reference/asciidoc/sm-papyrus.adoc
Normal 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%"]
|
||||
141
docs/src/reference/asciidoc/sm-persist.adoc
Normal file
141
docs/src/reference/asciidoc/sm-persist.adoc
Normal 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.
|
||||
253
docs/src/reference/asciidoc/sm-repository.adoc
Normal file
253
docs/src/reference/asciidoc/sm-repository.adoc
Normal 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]
|
||||
----
|
||||
====
|
||||
48
docs/src/reference/asciidoc/sm-scopes.adoc
Normal file
48
docs/src/reference/asciidoc/sm-scopes.adoc
Normal 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.
|
||||
286
docs/src/reference/asciidoc/sm-security.adoc
Normal file
286
docs/src/reference/asciidoc/sm-security.adoc
Normal 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`.
|
||||
14
docs/src/reference/asciidoc/sm-service.adoc
Normal file
14
docs/src/reference/asciidoc/sm-service.adoc
Normal 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`.
|
||||
40
docs/src/reference/asciidoc/sm-statecontext.adoc
Normal file
40
docs/src/reference/asciidoc/sm-statecontext.adoc
Normal 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>>).
|
||||
51
docs/src/reference/asciidoc/sm-test.adoc
Normal file
51
docs/src/reference/asciidoc/sm-test.adoc
Normal 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`].
|
||||
81
docs/src/reference/asciidoc/sm-triggers.adoc
Normal file
81
docs/src/reference/asciidoc/sm-triggers.adoc
Normal 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
Reference in New Issue
Block a user