From 6208b3eefe153ed0b886b7b15fee6581bd88805b Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Tue, 14 May 2019 23:10:35 +0100 Subject: [PATCH] Split sample docs - For sm-examples.adoc split every chapter into its own file to easy further reactive docs changes. - Relates #742 --- .../asciidoc/sm-examples-cdplayer.adoc | 295 +++ .../asciidoc/sm-examples-datajpa.adoc | 80 + .../sm-examples-datajpamultipersist.adoc | 101 + .../asciidoc/sm-examples-datajpapersist.adoc | 138 ++ .../asciidoc/sm-examples-datapersist.adoc | 138 ++ .../asciidoc/sm-examples-deploy.adoc | 55 + .../asciidoc/sm-examples-eventservice.adoc | 217 ++ .../asciidoc/sm-examples-monitoring.adoc | 137 ++ .../asciidoc/sm-examples-ordershipping.adoc | 52 + .../asciidoc/sm-examples-persist.adoc | 120 + .../reference/asciidoc/sm-examples-scope.adoc | 29 + .../asciidoc/sm-examples-security.adoc | 81 + .../asciidoc/sm-examples-showcase.adoc | 209 ++ .../reference/asciidoc/sm-examples-tasks.adoc | 214 ++ .../asciidoc/sm-examples-turnstile.adoc | 85 + .../asciidoc/sm-examples-washer.adoc | 98 + .../reference/asciidoc/sm-examples-web.adoc | 64 + .../asciidoc/sm-examples-zookeeper.adoc | 111 + docs/src/reference/asciidoc/sm-examples.adoc | 2117 +---------------- 19 files changed, 2242 insertions(+), 2099 deletions(-) create mode 100644 docs/src/reference/asciidoc/sm-examples-cdplayer.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-datajpa.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-datajpamultipersist.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-datajpapersist.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-datapersist.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-deploy.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-eventservice.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-monitoring.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-ordershipping.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-persist.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-scope.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-security.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-showcase.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-tasks.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-turnstile.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-washer.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-web.adoc create mode 100644 docs/src/reference/asciidoc/sm-examples-zookeeper.adoc diff --git a/docs/src/reference/asciidoc/sm-examples-cdplayer.adoc b/docs/src/reference/asciidoc/sm-examples-cdplayer.adoc new file mode 100644 index 00000000..3d8f6be4 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-cdplayer.adoc @@ -0,0 +1,295 @@ +[[statemachine-examples-cdplayer]] +== CD Player + +CD Player is a sample which resembles a use case that many people have +used in the real world. CD Player itself is a really simple entity that allows a +user to open a deck, insert or change a disk, and then drive the player's +functionality by pressing various buttons (`eject`, `play`, +`stop`, `pause`, `rewind`, and `backward`). + +How many of us have really given thought to what it will take to +make code that interacts with hardware to drive a CD Player. Yes, the +concept of a player is simple, but, if you look behind the scenes, +things actually get a bit convoluted. + +You have probably noticed that, if your deck is open and you press play, the +deck closes and a song starts to play (if a CD was inserted). +In a sense, when the deck is open, you first need to close +it and then try to start playing (again, if a CD is actually inserted). Hopefully, +you have now realized that a simple CD Player is so simple. +Sure, you can wrap all this with a simple class that has a few boolean variables +and probably a few nested if-else clauses. That will do the job, but what +about if you need to make all this behavior much more complex? Do you +really want to keep adding more flags and if-else clauses? + +The following image shows the state machine for our simple CD player: + +image::images/statechart3.png[width=500] + +The rest of this section goes through how this sample and its state machine is designed and +how those two interacts with each other. The following three configuration sections +are used within an `EnumStateMachineConfigurerAdapter`. + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetAA] +---- + +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetAB] +---- + +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetAC] +---- +==== + +In the preceding configuration: + +* We used `EnumStateMachineConfigurerAdapter` to configure states and + transitions. +* The `CLOSED` and `OPEN` states are defined as substates of `IDLE`, and + the `PLAYING` and `PAUSED` states are defined as substates of `BUSY`. +* With the `CLOSED` state, we added an entry action as a bean called + `closedEntryAction`. +* In the transitions we mostly map events to expected state + transitions, such as `EJECT` closing and opening a deck and `PLAY`, `STOP`, + and `PAUSE` doing their natural transitions. For other transitions, we did the following: +** For source state `PLAYING`, we added a timer trigger, which is +needed to automatically track elapsed time within a playing track and +to have a facility for making the decision about when to switch the to next track. +** For the `PLAY` event, if the source state is `IDLE` and the target state is + `BUSY`, we defined an action called `playAction` and a guard called `playGuard`. +** For the `LOAD` event and the `OPEN` state, we defined an internal +transition with an action called `loadAction`, which tracks inserting a disc with +extended-state variables. +** The `PLAYING` state defines three internal transitions. One is +triggered by a timer that runs an action called `playingAction`, which updates +the extended state variables. The other two transitions use `trackAction` +with different events (`BACK` and `FORWARD`, respectively) to handle +when the user wants to go back or forward in tracks. + +This machine has only have six states, which are defined by the following enumeration: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetB] +---- +==== + +Events represent the buttons the user can +press and whether the user loads a disc into the player. +The following enumeration defines the events: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetC] +---- +==== + +The `cdPlayer` and `library` beans are used to drive the application. +The following listing shows the definition of these two beans: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetD] +---- +==== + + +We define extended state variable keys as simple enumerations, +as the following listing shows: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetE] +---- +==== + +We wanted to make this sample type safe, so we define our own +annotation (`@StatesOnTransition`), which has a mandatory meta +annotation (`@OnTransition`). +The following listing defines the `@StatesOnTransition` annotation: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetF] +---- +==== + +`ClosedEntryAction` is an entry action for the `CLOSED` state, to +send a `PLAY` event to the state machine if a disc is present. +The following listing defines `ClosedEntryAction`: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetG] +---- +==== + +`LoadAction` update an extended state variable if event +headers contain information about a disc to load. +The following listing defines `LoadAction`: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetH] +---- +==== + +`PlayAction` resets the player's elapsed time, which is kept as +an extended state variable. +The following listing defines `PlayAction`: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetI] +---- +==== + +`PlayGuard` guards the transition from `IDLE` to `BUSY` with the +`PLAY` event if the `CD` extended state variable does not indicate that a +disc has been loaded. +The following listing defines `PlayGuard`: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetJ] +---- +==== + +`PlayingAction` updates an extended state variable called `ELAPSEDTIME`, which +the player can use to read and update its LCD status display. `PlayingAction` also handles +track shifting when the user goe back or forward in tracks. +The following example defines `PlayingAction`: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetK] +---- +==== + +`TrackAction` handles track shift actions when the user goes back or forward +in tracks. If a track is the last on a disc, playing is stopped and the `STOP` +event is sent to a state machine. +The following example defines `TrackAction`: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetL] +---- +==== + +One other important aspect of state machines is that they have their +own responsibilities (mostly around handling states) and that all application +level logic should be kept outside. This means that applications need +to have a ways to interact with a state machine. Also, note +that we annotated `CdPlayer` with `@WithStateMachine`, which instructs a +state machine to find methods from your POJO, which are then called +with various transitions. +The following example shows how it updates its LCD status display: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/CdPlayer.java[tags=snippetA] +---- +==== + +In the preceding example, we use the `@OnTransition` annotation to hook a callback +when a transition happens with a target state of `BUSY`. + +The following listing shows how our state machine handles whether the player is closed: + +==== +[source,java,indent=0] +---- +include::samples/demo/cdplayer/CdPlayer.java[tags=snippetB] +---- +==== + +`@OnTransition` (which we used in the preceding examples) can only be +used with strings that are matched from enumerations. `@StatesOnTransition` +lets you create your own type-safe annotations that use real enumerations. + +The following example shows how this state machine actually works. + +==== +[source,text] +---- +sm>sm start +Entry state IDLE +Entry state CLOSED +State machine started + +sm>cd lcd +No CD + +sm>cd library +0: Greatest Hits + 0: Bohemian Rhapsody 05:56 + 1: Another One Bites the Dust 03:36 +1: Greatest Hits II + 0: A Kind of Magic 04:22 + 1: Under Pressure 04:08 + +sm>cd eject +Exit state CLOSED +Entry state OPEN + +sm>cd load 0 +Loading cd Greatest Hits + +sm>cd play +Exit state OPEN +Entry state CLOSED +Exit state CLOSED +Exit state IDLE +Entry state BUSY +Entry state PLAYING + +sm>cd lcd +Greatest Hits Bohemian Rhapsody 00:03 + +sm>cd forward + +sm>cd lcd +Greatest Hits Another One Bites the Dust 00:04 + +sm>cd stop +Exit state PLAYING +Exit state BUSY +Entry state IDLE +Entry state CLOSED + +sm>cd lcd +Greatest Hits +---- +==== + +In the preceding run: + +* The state machine is started, which causes the machine to be initialized. +* The CD player's LCD screen status is printed. +* The CD library is printed. +* The CD player's deck is opened. +* The CD with index 0 is loaded into a deck. +* Play causes the deck to get closed and immediate play, because a disc + was inserted. +* We print the LCD status and request the next track. +* We stop playing. + diff --git a/docs/src/reference/asciidoc/sm-examples-datajpa.adoc b/docs/src/reference/asciidoc/sm-examples-datajpa.adoc new file mode 100644 index 00000000..e65cd192 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-datajpa.adoc @@ -0,0 +1,80 @@ +[[statemachine-examples-datajpa]] +== JPA Configuration + +The JPA configuration example shows how you can use state machine concepts +with a machine configuration kept in a database. This sample uses +an embedded H2 database with an H2 Console (to ease playing with the +database). + +This sample uses `spring-statemachine-autoconfigure` (which, by default, +auto-configures the repositories and entity classes needed for JPA). +Thus, you need only `@SpringBootApplication`. +The following example shows the `Application` class with the `@SpringBootApplication` annotation: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpa/Application.java[tags=snippetA] +---- +==== + +The following example shows how to create a `RepositoryStateMachineModelFactory`: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetB] +---- +==== + +You can use the following command to run the sample: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datajpa-{revnumber}.jar +---- +==== + +Accessing the application at `http://localhost:8080` brings up a newly +constructed machine for each request. You can then choose to send +events to a machine. The possible events and machine configuration are +updated from a database with every request. +The following image shows the UI and the initial events that are created when +this state machine starts: + +image::images/sm-datajpa-1.png[scaledwidth="100%"] + +To access the embedded console, you can use the JDBC URL (which is `jdbc:h2:mem:testdb`, if it is +not already set). +The following image shows the H2 console: + +image::images/sm-datajpa-2.png[scaledwidth="100%"] + +From the console, you can see the database tables and modify +them as you wish. +The following image shows the result of a simple query in the UI: + +image::images/sm-datajpa-3.png[scaledwidth="100%"] + +Now that you have gotten this far, you have probably wondered how those default +states and transitions got populated into the database. Spring Data +has a nice trick to auto-populate repositories, and we +used this feature through `Jackson2RepositoryPopulatorFactoryBean`. +The following example shows how we create such a bean: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetA] +---- +==== + +The following listing shows the source of the data with which we populate the database: + +==== +[source,json,indent=0] +---- +include::samples/data.json[] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-examples-datajpamultipersist.adoc b/docs/src/reference/asciidoc/sm-examples-datajpamultipersist.adoc new file mode 100644 index 00000000..b9fac872 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-datajpamultipersist.adoc @@ -0,0 +1,101 @@ +[[statemachine-examples-datajpamultipersist]] +== Data Multi Persist + +The data multi ersist sample is an extension of two other samples: +<> and <>. +We still keep machine configuration in a database and persist into a +database. However, this time, we also have a machine that contains two orthogonal +regions, to show how those are persisted independently. This sample +also uses an embedded H2 database with an H2 Console (to ease playing +with the database). + +This sample uses `spring-statemachine-autoconfigure` (which, by default, +auto-configures the repositories and entity classes needed for JPA). +Thus, you need only `@SpringBootApplication`. +The following example shows the `Application` class with the `@SpringBootApplication` annotation: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpamultipersist/Application.java[tags=snippetA] +---- +==== + +As in the other data-driven samples, we again create a `StateMachineRuntimePersister`, +as the following listing shows: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetA] +---- +==== + +A `StateMachineService` bean makes it easier to work with a machines. +The following listing shows how to create such a bean: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetB] +---- +==== + +We use JSON data to import the configuration. +The following example creates a bean to do so: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetC] +---- +==== + +The following listing shows how we get a `RepositoryStateMachineModelFactory`: + +==== +[source,java,indent=0] +---- +include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetD] +---- +==== + +You can run the sample by using the following command: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datajpamultipersist-{revnumber}.jar +---- +==== + +Accessing the application at `http://localhost:8080` brings up a newly +constructed machine for each request and lets you send +events to a machine. The possible events and the state machine configuration are +updated from a database for each request. We also print out +all state machine contexts and the current root machine, +as the following image shows: + +image::images/sm-datajpamultipersist-1.png[scaledwidth="100%"] + +The state machine named `datajpamultipersist1` is a simple "`flat`" machine where states `S1`, +`S2` and `S3` are transitioned by events `E1`, `E2`, and `E3` (respectively). +However, the state machine named `datajpamultipersist2` contains two +regions (`R1` and `R2`) directly under the root level. That is why this +root level machine really does not have a state. We need +that root level machine to host those regions. + +Regions `R1` and `R2` in the `datajpamultipersist2` state machine contains states +`S10`, `S11`, and `S12` and `S20`, `S21`, and `S22` (respectively). Events +`E10`, `E11`, and `E12` are used for region `R1` and events `E20`, `E21`, +and event `E22` is used for region `R2`. The following images shows what happens when we +send events `E10` and `E20` to the +`datajpamultipersist2` state machine: + +image::images/sm-datajpamultipersist-2.png[scaledwidth="100%"] + +Regions have their own contexts with their own IDs, and the actual +ID is postfixed with `#` and the region ID. As the following image shows, +different regions in a database have different contexts: + +image::images/sm-datajpamultipersist-3.png[scaledwidth="100%"] diff --git a/docs/src/reference/asciidoc/sm-examples-datajpapersist.adoc b/docs/src/reference/asciidoc/sm-examples-datajpapersist.adoc new file mode 100644 index 00000000..0e2d57f4 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-datajpapersist.adoc @@ -0,0 +1,138 @@ +[[statemachine-examples-datapersist]] +== Data Persist + +The data persist sample shows how you can state machine concepts +with a persisting machine in an external repository. This sample uses +an embedded H2 database with an H2 Console (to ease playing with the +database). Optionally, you can also enable Redis or MongoDB. + +This sample uses `spring-statemachine-autoconfigure` (which, by default, +auto-configures the repositories and entity classes needed for JPA). +Thus, you need only `@SpringBootApplication`. +The following example shows the `Application` class with the `@SpringBootApplication` annotation: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/Application.java[tags=snippetA] +---- +==== + +The `StateMachineRuntimePersister` interface works on the runtime +level of a `StateMachine`. Its implementation, +`JpaPersistingStateMachineInterceptor`, is meant to be used with a +JPA. +The following listing creates a `StateMachineRuntimePersister` bean: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetA] +---- +==== + +The following example shows how you can use a very similar configuration +to create a bean for MongoDB: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetB] +---- +==== + +The following example shows how you can use a very similar configuration +to create a bean for Redis: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetC] +---- +==== + +You can configure `StateMachine` to use runtime persistence by using the +`withPersistence` configuration method. +The following listing shows how to do so: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetD] +---- +==== + +This sample also uses `DefaultStateMachineService`, which makes it +easier to work with multiple machines. +The following listing shows how to create an instance of `DefaultStateMachineService`: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetE] +---- +==== + +The following listing shows the logic that drives the `StateMachineService` in this sample: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineController.java[tags=snippetA] +---- +==== + +You can use the following command to run the sample: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar +---- +==== + +[NOTE] +==== +By default, the `jpa` profile is enabled in `application.yml`. If you want to try +other backends, enable either the `mongo` profile or the `redis` profile. +The following commands specify which profile to use (`jpa` is the default, +but we included it for the sake of completeness): + +===== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=jpa +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=mongo +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=redis +---- +===== +==== + +Accessing the application at http://localhost:8080 brings up a newly +constructed state machine for each request, and you can choose to send +events to a machine. The possible events and machine configuration are +updated from a database with every request. + +The state machines in this sample have a simple configuration with states 'S1' +to 'S6' and events 'E1' to 'E6' to transition the state machine between those +states. You can use two state machine identifiers (`datajpapersist1` and +`datajpapersist2`) to request a particular state machine. +The following image shows the UI that lets you pick a machine and an event and that shows +what happens when you do: + +image::images/sm-datajpapersist-1.png[scaledwidth="100%"] + +The sample defaults to using machine 'datajpapersist1' and goes to its +initial state 'S1'. +The following image shows the result of using those defaults: + +image::images/sm-datajpapersist-2.png[scaledwidth="100%"] + +If you send events `E1` and `E2` to the `datajpapersist1` state machine, its +state is persisted as 'S3'. +The following image shows the result of doing so: + +image::images/sm-datajpapersist-3.png[scaledwidth="100%"] + +If you then request state machine `datajpapersist1` but send no events, +the state machine is restored back to its persisted state, `S3`. diff --git a/docs/src/reference/asciidoc/sm-examples-datapersist.adoc b/docs/src/reference/asciidoc/sm-examples-datapersist.adoc new file mode 100644 index 00000000..0e2d57f4 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-datapersist.adoc @@ -0,0 +1,138 @@ +[[statemachine-examples-datapersist]] +== Data Persist + +The data persist sample shows how you can state machine concepts +with a persisting machine in an external repository. This sample uses +an embedded H2 database with an H2 Console (to ease playing with the +database). Optionally, you can also enable Redis or MongoDB. + +This sample uses `spring-statemachine-autoconfigure` (which, by default, +auto-configures the repositories and entity classes needed for JPA). +Thus, you need only `@SpringBootApplication`. +The following example shows the `Application` class with the `@SpringBootApplication` annotation: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/Application.java[tags=snippetA] +---- +==== + +The `StateMachineRuntimePersister` interface works on the runtime +level of a `StateMachine`. Its implementation, +`JpaPersistingStateMachineInterceptor`, is meant to be used with a +JPA. +The following listing creates a `StateMachineRuntimePersister` bean: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetA] +---- +==== + +The following example shows how you can use a very similar configuration +to create a bean for MongoDB: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetB] +---- +==== + +The following example shows how you can use a very similar configuration +to create a bean for Redis: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetC] +---- +==== + +You can configure `StateMachine` to use runtime persistence by using the +`withPersistence` configuration method. +The following listing shows how to do so: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetD] +---- +==== + +This sample also uses `DefaultStateMachineService`, which makes it +easier to work with multiple machines. +The following listing shows how to create an instance of `DefaultStateMachineService`: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetE] +---- +==== + +The following listing shows the logic that drives the `StateMachineService` in this sample: + +==== +[source,java,indent=0] +---- +include::samples/demo/datapersist/StateMachineController.java[tags=snippetA] +---- +==== + +You can use the following command to run the sample: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar +---- +==== + +[NOTE] +==== +By default, the `jpa` profile is enabled in `application.yml`. If you want to try +other backends, enable either the `mongo` profile or the `redis` profile. +The following commands specify which profile to use (`jpa` is the default, +but we included it for the sake of completeness): + +===== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=jpa +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=mongo +# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=redis +---- +===== +==== + +Accessing the application at http://localhost:8080 brings up a newly +constructed state machine for each request, and you can choose to send +events to a machine. The possible events and machine configuration are +updated from a database with every request. + +The state machines in this sample have a simple configuration with states 'S1' +to 'S6' and events 'E1' to 'E6' to transition the state machine between those +states. You can use two state machine identifiers (`datajpapersist1` and +`datajpapersist2`) to request a particular state machine. +The following image shows the UI that lets you pick a machine and an event and that shows +what happens when you do: + +image::images/sm-datajpapersist-1.png[scaledwidth="100%"] + +The sample defaults to using machine 'datajpapersist1' and goes to its +initial state 'S1'. +The following image shows the result of using those defaults: + +image::images/sm-datajpapersist-2.png[scaledwidth="100%"] + +If you send events `E1` and `E2` to the `datajpapersist1` state machine, its +state is persisted as 'S3'. +The following image shows the result of doing so: + +image::images/sm-datajpapersist-3.png[scaledwidth="100%"] + +If you then request state machine `datajpapersist1` but send no events, +the state machine is restored back to its persisted state, `S3`. diff --git a/docs/src/reference/asciidoc/sm-examples-deploy.adoc b/docs/src/reference/asciidoc/sm-examples-deploy.adoc new file mode 100644 index 00000000..082de2ba --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-deploy.adoc @@ -0,0 +1,55 @@ +[[statemachine-examples-deploy]] +== Deploy + +The deploy example shows how you can use state machine concepts with +UML modeling to provide a generic error handling state. This state +machine is a relatively complex example of how you can use various features +to provide a centralized error handling concept. +The following image shows the deploy state machine: + +image::images/model-deployer.png[width=500] + +NOTE: The preceding state chart was designed by using the Eclipse Papyrus Plugin +(see<>) and imported into Spring StateMachine through the resulting UML +model file. Actions and guards defined in a model are resolved +from a Spring Application Context. + +In this state machine scenario, we have two different behaviors +(`DEPLOY` and `UNDEPLOY`) that user tries to execute. + +In the preceding state chart: + +* In the `DEPLOY` state, the `INSTALL` and `START` states are entered + conditionally. We enter `START` directly if a product is already + installed and have no need to try to `START` if install fails. +* In the `UNDEPLOY` state, we enter `STOP` conditionally if the application is + already running. +* Conditional choices for `DEPLOY` and `UNDEPLOY` are done through a + choice pseudostate within those states, and the choices are selected + by guards. +* We use exit point pseudostates to have a more controlled exit from the + `DEPLOY` and `UNDEPLOY` states. +* After exiting from `DEPLOY` and `UNDEPLOY`, we go through a junction + pseudostate to choose whether to go through an `ERROR` state + (if an error was added into an extended state). +* Finally, we go back to the `READY` state to process new requests. + +Now we can get to the actual demo. Run the boot based sample application +by running the following command: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-deploy-{revnumber}.jar +---- +==== + +In a browser, you can see something like the following image: + +image::images/sm-deploy-1.png[width=500] + +IMPORTANT: As we do not have real install, start, or stop functionality, we +simulate failures by checking the existence of particular message headers. + +Now you can start to send events to a machine and choose various +message headers to drive functionality. diff --git a/docs/src/reference/asciidoc/sm-examples-eventservice.adoc b/docs/src/reference/asciidoc/sm-examples-eventservice.adoc new file mode 100644 index 00000000..a2d0368f --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-eventservice.adoc @@ -0,0 +1,217 @@ +[[statemachine-examples-eventservice]] +== Event Service + +The event service example shows how you can use state machine concepts as +a processing engine for events. This sample evolved from a question: + +Can I use Spring Statemachine as a microservice to feed events to +different state machine instances? In fact, Spring Statemachine can feed +events to potentially millions of different state machine instances. + +This example uses a `Redis` instance to persist state machine +instances. + +Obviously, a million state machine instances in a JVM would be +a bad idea, due to memory constraints. This leads to +other features of Spring Statemachine that let you persist a +`StateMachineContext` and re-use existing instances. + +For this example, we assume that a shopping application +sends different types of `PageView` events to a separate +microservice which then tracks user behavior by using a state +machine. The following image shows the state model, which has a few states +that represent a user navigating a product items list, adding and removing +items from a cart, going to a payment page, and initiating a payment +operation: + +image::images/statechart14.png[width=500] + +An actual shopping application would send these events into +this service by (for example) using a rest call. More about this +later. + +NOTE: Remember that the focus here is to have an application that exposes a +`REST` API that the user can use to send events that can be processed by a +state machine for each request. + +The following state machine configuration models what we have in a +state chart. Various actions update the state machine's `Extended +State` to track the number of entries into various states and also how +many times the internal transitions for `ADD` and `DEL` are called and whether +`PAY` has been executed: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetC] +---- +==== + +Do not focus on `stateMachineTarget` or +`@Scope` for now, as we explain those later in this section. + +We set up a `RedisConnectionFactory` that defaults to +localhost and default port. We use `StateMachinePersist` with a +`RepositoryStateMachinePersist` implementation. Finally, we create a +`RedisStateMachinePersister` that uses a previously +created `StateMachinePersist` bean. + +These are then used in a `Controller` that handles `REST` calls, +as the following listing shows: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetD] +---- +==== + +We create a bean named `stateMachineTarget`. +State machine instantiation is a relatively +expensive operation, so it is better to try to pool instances instead +of instantiating a new instance for every request. To do so, we first +create a `poolTargetSource` that wraps `stateMachineTarget` and pools +it with a max size of three. When then proxy this `poolTargetSource` with +`ProxyFactoryBean` by using a `request` scope. Effectively, this means +that every `REST` request gets a pooled state machine instance from +a bean factory. Later, we show how these instances are used. +The following listing shows how we create the `ProxyFactoryBean` +and set the target source: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetA] +---- +==== + +The following listing shows we set the maximum size and set the target bean name: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetB] +---- +==== + +Now we can get into actual demo. You need to have a Redis server running on +localhost with default settings. Then you need to run the Boot-based sample +application by running the following command: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-eventservice-{revnumber}.jar +---- +==== + +In a browser, you see something like the following: + +image::images/sm-eventservice-1.png[width=500] + +In this UI, you can use three users: `joe`, `bob`, and `dave`. +Clicking a button shows the current state and the extended state. Enabling a +radio button before clicking a button sends a particular event for that +user. This arrangement lets you play with the UI. + +In our `StateMachineController`, we autowire `StateMachine` and +`StateMachinePersister`. `StateMachine` is `request` scoped, so you +get a new instance for each request, while `StateMachinePersist` is a normal +singleton bean. +The following listing autowires `StateMachine` and +`StateMachinePersist`: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineController.java[tags=snippetA] +---- +==== + +In the following listing, `feedAndGetState` is used with a UI to do same things that an +actual `REST` api might do: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineController.java[tags=snippetB] +---- +==== + +In the following listing, `feedPageview` is a `REST` method that accepts a post with +JSON content. + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineController.java[tags=snippetC] +---- +==== + +In the following listing, `feedMachine` sends an event into a `StateMachine` and persists +its state by using a `StateMachinePersister`: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineController.java[tags=snippetD] +---- +==== + +The following listing shows a `resetStateMachineFromStore` that is used to restore a state machine +for a particular user: + +==== +[source,java,indent=0] +---- +include::samples/demo/eventservice/StateMachineController.java[tags=snippetE] +---- +==== + +As you would usually send an event by using a UI, you can do the same by using `REST` calls, +as the following curl command shows: + +==== +[source,text] +---- +# curl http://localhost:8080/feed -H "Content-Type: application/json" --data '{"user":"joe","id":"VIEW_I"}' +---- +==== + +At this point, you should have content in Redis with a key of +`testprefix:joe`, as the following example shows: + +==== +[source,text] +---- +$ ./redis-cli +127.0.0.1:6379> KEYS * +1) "testprefix:joe" +---- +==== + +The next three images show when state for `joe` has been changed from +`HOME` to `ITEMS` and when the `ADD` action has been executed. + +The following image the `ADD` event being sent: + +image::images/sm-eventservice-2.png[width=500] + +Now your are still on the `ITEMS` state, and the internal transition caused +the `COUNT` extended state variable to increase to `1`, as the following image shows: + +image::images/sm-eventservice-3.png[width=500] + +Now you can run the following `curl` rest call a few times (or do it through the UI) and +see the `COUNT` variable increase with every call: + +==== +[source,text] +---- +# curl http://localhost:8080/feed -H "Content-Type: application/json" # --data '{"user":"joe","id":"ADD"}' +---- +==== + +The following image shows the result of these operations: + +image::images/sm-eventservice-4.png[width=500] diff --git a/docs/src/reference/asciidoc/sm-examples-monitoring.adoc b/docs/src/reference/asciidoc/sm-examples-monitoring.adoc new file mode 100644 index 00000000..7cf6f3fe --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-monitoring.adoc @@ -0,0 +1,137 @@ +[[statemachine-examples-monitoring]] +== Monitoring + +The monitoring sample shows how you can use state machine concepts to +monitor state machine transitions and actions. +The following listing configures the state machine that we use for this sample: + +==== +[source,java,indent=0] +---- +include::samples/demo/monitoring/StateMachineConfig.java[tags=snippetA] +---- +==== + +You can use the following command to run the sample: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-monitoring-{revnumber}.jar +---- +==== + +The following image shows the state machine's initial state: + +image::images/sm-monitoring-1.png[scaledwidth="100%"] + +The following image shows the state of the state machine after we have +performed some actions: + +image::images/sm-monitoring-2.png[scaledwidth="100%"] + +You can view metrics from Spring Boot by running the following two `curl` +commands (shown with their output): + +==== +[source,json] +---- +# curl http://localhost:8080/actuator/metrics/ssm.transition.duration + +{ + "name":"ssm.transition.duration", + "measurements":[ + { + "statistic":"COUNT", + "value":3.0 + }, + { + "statistic":"TOTAL_TIME", + "value":0.007 + }, + { + "statistic":"MAX", + "value":0.004 + } + ], + "availableTags":[ + { + "tag":"transitionName", + "values":[ + "INITIAL_S1", + "EXTERNAL_S1_S2" + ] + } + ] +} +---- + +[source,json] +---- +# curl http://localhost:8080/actuator/metrics/ssm.transition.transit + +{ + "name":"ssm.transition.transit", + "measurements":[ + { + "statistic":"COUNT", + "value":3.0 + } + ], + "availableTags":[ + { + "tag":"transitionName", + "values":[ + "EXTERNAL_S1_S2", + "INITIAL_S1" + ] + } + ] +} +---- +==== + +You can also view tracing from Spring Boot by running the following `curl` +command (shown with its output): + +==== +[source,json] +---- +# curl http://localhost:8080/actuator/statemachinetrace + +[ + { + "timestamp":"2018-02-11T06:44:12.723+0000", + "info":{ + "duration":2, + "machine":null, + "transition":"EXTERNAL_S1_S2" + } + }, + { + "timestamp":"2018-02-11T06:44:12.720+0000", + "info":{ + "duration":0, + "machine":null, + "action":"demo.monitoring.StateMachineConfig$Config$$Lambda$576/1499688007@22b47b2f" + } + }, + { + "timestamp":"2018-02-11T06:44:12.714+0000", + "info":{ + "duration":1, + "machine":null, + "transition":"INITIAL_S1" + } + }, + { + "timestamp":"2018-02-11T06:44:09.689+0000", + "info":{ + "duration":4, + "machine":null, + "transition":"INITIAL_S1" + } + } +] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-examples-ordershipping.adoc b/docs/src/reference/asciidoc/sm-examples-ordershipping.adoc new file mode 100644 index 00000000..5350dd7d --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-ordershipping.adoc @@ -0,0 +1,52 @@ +[[statemachine-examples-ordershipping]] +== Order Shipping + +The order shipping example shows how you can use state machine concepts +to build a simple order processing system. + +The following image shows a state chart that drives this order shipping sample. + +image::images/sm-ordershipping-1.png[scaledwidth="100%"] + +In the preceding state chart: + +* The state machine enters the `WAIT_NEW_ORDER` (default) state. +* The event `PLACE_ORDER` transitions into the `RECEIVE_ORDER` state and the entry + action (`entryReceiveOrder`) is executed. +* If the order is `OK`, the state machine goes into two regions, one handling order + production and one handling user-level payment. Otherwise, the state machine goes + into `CUSTOMER_ERROR`, which is a final state. +* The state machine loops in a lower region to remind the user to pay + until `RECEIVE_PAYMENT` is sent successfully to indicate correct + payment. +* Both regions go into waiting states (`WAIT_PRODUCT` and + `WAIT_ORDER`), where they are joined before the parent orthogonal state + (`HANDLE_ORDER`) is exited. +* Finally, the state machine goes through `SHIP_ORDER` to its final state + (`ORDER_SHIPPED`). + +The following command runs the sample: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-ordershipping-{revnumber}.jar +---- +==== + +In a browser, you can see something similar to the following image. You can start by choosing +a customer and an order to create a state machine. + +image::images/sm-ordershipping-2.png[scaledwidth="100%"] + +The state machine for a particular order is now created and you can start to play +with placing an order and sending a payment. Other settings (such as +`makeProdPlan`, `produce`, and `payment`) let you control how the state +machine works. +The following image shows the state machine waiting for an order: + +image::images/sm-ordershipping-3.png[scaledwidth="100%"] + +Finally, you can see what machine does by refreshing a page, as the following image shows: + +image::images/sm-ordershipping-4.png[scaledwidth="100%"] diff --git a/docs/src/reference/asciidoc/sm-examples-persist.adoc b/docs/src/reference/asciidoc/sm-examples-persist.adoc new file mode 100644 index 00000000..03b822c3 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-persist.adoc @@ -0,0 +1,120 @@ +[[statemachine-examples-persist]] +== Persist + +Persist is a sample that uses the <> recipe to +demonstrate how database entry update logic can be controlled by a +state machine. + +The following image shows the state machine logic and configuration: + +image::images/statechart10.png[width=500] + +The following listing shows the state machine configuration: + +==== +.StateMachine Config +[source,java,indent=0] +---- +include::samples/demo/persist/Application.java[tags=snippetA] +---- +==== + +The following configuration creates `PersistStateMachineHandler`: + +==== +.Handler Config +[source,java,indent=0] +---- +include::samples/demo/persist/Application.java[tags=snippetB] +---- +==== + +The following listing shows the `Order` class used with this sample: + +==== +.Order Class +[source,java,indent=0] +---- +include::samples/demo/persist/Application.java[tags=snippetC] +---- +==== + +The following example shows the state machine's output: + +==== +[source,text] +---- +sm>persist db +Order [id=1, state=PLACED] +Order [id=2, state=PROCESSING] +Order [id=3, state=SENT] +Order [id=4, state=DELIVERED] + +sm>persist process 1 +Exit state PLACED +Entry state PROCESSING + +sm>persist db +Order [id=2, state=PROCESSING] +Order [id=3, state=SENT] +Order [id=4, state=DELIVERED] +Order [id=1, state=PROCESSING] + +sm>persist deliver 3 +Exit state SENT +Entry state DELIVERED + +sm>persist db +Order [id=2, state=PROCESSING] +Order [id=4, state=DELIVERED] +Order [id=1, state=PROCESSING] +Order [id=3, state=DELIVERED] +---- +==== + +In the preceding run, the state machine: + +* Listed rows from an existing embedded database, which is already + populated with sample data. +* Requested to update order `1` into the `PROCESSING` state. +* List database entries again and see that the state has been changed from + `PLACED` to `PROCESSING`. +* Update order `3` to update its state from `SENT` to + `DELIVERED`. + +[NOTE] +==== +You may wonder where the database is, because there are literally no +signs of it in the sample code. The sample is based on Spring Boot and, +because the necessary classes are in a classpath, an embedded `HSQL` instance +is created automatically. + +Spring Boot even creates an instance of `JdbcTemplate`, which you +can autowire, as we did in `Persist.java`, shown in the following listing: + +===== +[source,java,indent=0] +---- +include::samples/demo/persist/Persist.java[tags=snippetA] +---- +===== +==== + +Next, we need to handle state changes. The following listing shows how we do so: + +==== +[source,java,indent=0] +---- +include::samples/demo/persist/Persist.java[tags=snippetB] +---- +==== + +Finally, we use a `PersistStateChangeListener` to update the database, as the +following listing shows: + +==== +[source,java,indent=0] +---- +include::samples/demo/persist/Persist.java[tags=snippetC] +---- +==== diff --git a/docs/src/reference/asciidoc/sm-examples-scope.adoc b/docs/src/reference/asciidoc/sm-examples-scope.adoc new file mode 100644 index 00000000..1118580b --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-scope.adoc @@ -0,0 +1,29 @@ +[[statemachine-examples-scope]] + +== Scope + +Scope is a state machine example that uses session scope to provide an +individual instance for every user. +The following image shows the states and events within the Scope state machine: + +image::images/statechart12.png[width=500] + +This simple state machine has three states: `S0`, `S1`, and `S2`. +Transitions between those are controlled by three events: `A`, `B`, and `C`. + +To start the state machine, run the following command in a terminal: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-scope-{revnumber}.jar +---- +==== + +When the instance is running, you can open a browser and play with the state +machine. If you open the same page in a different browser, (for example, one in +Chrome and one in Firefox), you should get a new state machine +instance for each user session. +The following image shows the state machine in a browser: + +image::images/sm-scope-1.png[width=500] diff --git a/docs/src/reference/asciidoc/sm-examples-security.adoc b/docs/src/reference/asciidoc/sm-examples-security.adoc new file mode 100644 index 00000000..afafcea1 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-security.adoc @@ -0,0 +1,81 @@ +[[statemachine-examples-security]] +== Security + +Security is a state machine example that uses most of the possible combinations of +securing a state machine. It secures sending events, transitions, +and actions. +The following image shows the state machine's states and events: + +image::images/statechart13.png[width=500] + +To start the state machine, run the following command: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-secure-{revnumber}.jar +---- +==== + +We secure event sending by requiring that users have a role of `USER`. +Spring Security ensures that no other users can send events to this +state machine. +The following listing secures event sending: + +==== +[source,java,indent=0] +---- +include::samples/demo/security/StateMachineConfig.java[tags=snippetA] +---- +==== + +In this sample we define two users: + +* A user named `user` who has a role of `USER` +* A user named `admin` who has two roles: `USER` and `ADMIN` + +The password for both users is `password`. +The following listing configures the two users: + +==== +[source,java,indent=0] +---- +include::samples/demo/security/StateMachineConfig.java[tags=snippetE] +---- +==== + +We define various transitions between states according to the state chart +shown at the beginning of the example. Only a user with an active `ADMIN` role can run +the external transitions between `S2` and `S3`. Similarly only an `ADMIN` can +run the internal transition the `S1` state. +The following listing defines the transitions, including their security: + +==== +[source,java,indent=0] +---- +include::samples/demo/security/StateMachineConfig.java[tags=snippetB] +---- +==== + +The following listing uses a method called `adminAction` whose return type is `Action` to +specify that the action is secured with a role of `ADMIN`: + +==== +[source,java,indent=0] +---- +include::samples/demo/security/StateMachineConfig.java[tags=snippetC] +---- +==== + +The following `Action` runs an internal transition in state `S` when event `F` is sent. + +==== +[source,java,indent=0] +---- +include::samples/demo/security/StateMachineConfig.java[tags=snippetD] +---- +==== + +The transition itself is secured with a +role of `ADMIN`, so this transition does not run if the current user +does not hate that role. diff --git a/docs/src/reference/asciidoc/sm-examples-showcase.adoc b/docs/src/reference/asciidoc/sm-examples-showcase.adoc new file mode 100644 index 00000000..146d4ca9 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-showcase.adoc @@ -0,0 +1,209 @@ +[[statemachine-examples-showcase]] +== Showcase + +Showcase is a complex state machine that shows all possible transition +topologies up to four levels of state nesting. +The following image shows the state machine: + +image::images/statechart2.png[width=500] + +The following listing shows the enumeration that defines the possible states: + +==== +.States +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetB] +---- +==== + +The following listing shows the enumeration that defines the events: + +==== +.Events +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetC] +---- +==== + +The following listing shows the code that configures the state machine: + +==== +.Configuration - states +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetAA] +---- +==== + +The following listing shows the code that configures the state machine's transitions: + +==== +.Configuration - transitions +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetAB] +---- +==== + +The following listing shows the code that configures the state machine's actions and guards: + +==== +.Configuration - actions and guards +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetAC] +---- +==== + +The following listing shows how the single action is defined: + +==== +.Action +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetD] +---- +==== + +The following listing shows how the single guard is defined: + +==== +.Guard +[source,java,indent=0] +---- +include::samples/demo/showcase/Application.java[tags=snippetE] +---- +==== + +The following listing shows the output that this state machine produces when it runs and +various events are sent to it: + +==== +[source,text] +---- +sm>sm start +Init foo to 0 +Entry state S0 +Entry state S1 +Entry state S11 +State machine started + +sm>sm event A +Event A send + +sm>sm event C +Exit state S11 +Exit state S1 +Entry state S2 +Entry state S21 +Entry state S211 +Event C send + +sm>sm event H +Switch foo to 1 +Internal transition source=S0 +Event H send + +sm>sm event C +Exit state S211 +Exit state S21 +Exit state S2 +Entry state S1 +Entry state S11 +Event C send + +sm>sm event A +Exit state S11 +Exit state S1 +Entry state S1 +Entry state S11 +Event A send +---- +==== + +In the preceding output, we can see that: + +* The state machine is started, which takes it to its initial state (`S11`) + through superstates (`S1`) and (`S0`). Also, the extended state variable, `foo`, is + initialized to `0`. +* We try to execute a self transition in state `S1` with event `A`, but + nothing happens because the transition is guarded by variable `foo` to + be `1`. +* We send event `C`, which takes us to the other state machine, where + the initial state (`S211`) and its superstates are entered. In there, we + can use event `H`, which does a simple internal transition to flip the + `foo` variable. Then we go back by using event `C`. +* Event `A` is sent again, and now `S1` does a self transition because the + guard evaluates to `true`. + +The following example offers a closer look at how hierarchical states and their event +handling works: + +==== +[source,text] +---- +sm>sm variables +No variables + +sm>sm start +Init foo to 0 +Entry state S0 +Entry state S1 +Entry state S11 +State machine started + +sm>sm variables +foo=0 + +sm>sm event H +Internal transition source=S1 +Event H send + +sm>sm variables +foo=0 + +sm>sm event C +Exit state S11 +Exit state S1 +Entry state S2 +Entry state S21 +Entry state S211 +Event C send + +sm>sm variables +foo=0 + +sm>sm event H +Switch foo to 1 +Internal transition source=S0 +Event H send + +sm>sm variables +foo=1 + +sm>sm event H +Switch foo to 0 +Internal transition source=S2 +Event H send + +sm>sm variables +foo=0 +---- +==== + +In the preceding sample: + +* We print extended state variables in various stages. +* With event `H`, we end up running an internal transition, + which is logged with its source state. +* Note how event `H` is handled in + different states (`S0`, `S1`, and `S2`). This is a good example of how + hierarchical states and their event handling works. If state `S2` is + unable to handle event `H` due to a guard condition, its parent is + checked next. This guarantees that, while the machine is on state `S2`, the `foo` flag + is always flipped around. However, in state `S1`, event `H` always + matches to its dummy transition without guard or action, so it never + happens. + diff --git a/docs/src/reference/asciidoc/sm-examples-tasks.adoc b/docs/src/reference/asciidoc/sm-examples-tasks.adoc new file mode 100644 index 00000000..4a1360c4 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-tasks.adoc @@ -0,0 +1,214 @@ +[[statemachine-examples-tasks]] +== Tasks + +The Tasks sample demonstrates parallel task handling within +regions and adds error handling to either +automatically or manually fix task problems before continuing back +to a state where the tasks can be run again. +The following image shows the Tasks state machine: + +image::images/statechart5.png[width=500] + +On a high level, in this state machine: + +* We always try to get into the `READY` state so that we can use the + RUN event to execute tasks. +* Tkhe `TASKS` state, which is composed of three independent regions, has been + put in the middle of `FORK` and `JOIN` states, which will cause the regions to + go into their initial states and to be joined by their end states. +* From the `JOIN` state, we automatically go into a `CHOICE` state, which checks + for the existence of error flags in extended state variables. Tasks can set + these flags, and doing so gives the `CHOICE` state the ability to go into the `ERROR` + state, where errors can be handled either automatically or manually. +* The `AUTOMATIC` state in `ERROR` can try to automatically fix an error and goes + back to `READY` if it succeeds. If the error is something what + cannot be handled automatically, user intervention is needed and the + machine is put into the `MANUAL` state by the `FALLBACK` event. + +The following listing shows the enumeration that defines the possible states: + +==== +.States +[source,java,indent=0] +---- +include::samples/demo/tasks/Application.java[tags=snippetB] +---- +==== + +The following listing shows the enumeration that defines the events: + +==== +.Events +[source,java,indent=0] +---- +include::samples/demo/tasks/Application.java[tags=snippetC] +---- +==== + +The following listing configures the possible states: + +==== +.Configuration - states +[source,java,indent=0] +---- +include::samples/demo/tasks/Application.java[tags=snippetAA] +---- +==== + +The following listing configures the possible transitions: + +==== +.Configuration - transitions +[source,java,indent=0] +---- +include::samples/demo/tasks/Application.java[tags=snippetAB] +---- +==== + +The following guard sends a choice entry into the `ERROR` state and needs to +return `TRUE` if an error has happened. This guard checks that +all extended state variables(`T1`, `T2`, and `T3`) are `TRUE`. + +==== +[source,java,indent=0] +---- +include::samples/demo/tasks/Application.java[tags=snippetAC] +---- +==== + +The following actions below send events to the state machine to request +the next step, which is either to fall back or to continue back to ready. + +==== +[source,java,indent=0] +---- +include::samples/demo/tasks/Application.java[tags=snippetAD] +---- +==== + +Currently, the default region execution is synchronous, but you can change +it to be asynchronous by changing `TaskExecutor`. Task simulates +work by sleeping two seconds so that you can see how actions in +regions are executed inparallel. +The following listing shows the `TaskExecutor` bean definition: + +==== +[source,java,indent=0] +---- +include::samples/demo/tasks/Application.java[tags=snippetAE] +---- +==== + +The following example shows how this state machine actually works: + +==== +[source,text] +---- +sm>sm start +State machine started +Entry state READY + +sm>tasks run +Entry state TASKS +run task on T3 +run task on T2 +run task on T1 +run task on T2 done +run task on T1 done +run task on T3 done +Entry state T2 +Entry state T3 +Entry state T1 +Entry state T1E +Entry state T2E +Entry state T3E +Exit state TASKS +Entry state JOIN +Exit state JOIN +Entry state READY +---- +==== + +In the preceding listing, we can see that tasks run multiple times. +In the next listing, we introduce errors: + +==== +[source,text] +---- +sm>tasks list +Tasks {T1=true, T3=true, T2=true} + +sm>tasks fail T1 + +sm>tasks list +Tasks {T1=false, T3=true, T2=true} + +sm>tasks run +Entry state TASKS +run task on T1 +run task on T3 +run task on T2 +run task on T1 done +run task on T3 done +run task on T2 done +Entry state T1 +Entry state T3 +Entry state T2 +Entry state T1E +Entry state T2E +Entry state T3E +Exit state TASKS +Entry state JOIN +Exit state JOIN +Entry state ERROR +Entry state AUTOMATIC +Exit state AUTOMATIC +Exit state ERROR +Entry state READY +---- +==== + +In the preceding listing, if we simulate a failure for task T1, it is fixed +automatically. +In the next listing, we introduce more errors: + +==== +[source,text] +---- +sm>tasks list +Tasks {T1=true, T3=true, T2=true} + +sm>tasks fail T2 + +sm>tasks run +Entry state TASKS +run task on T2 +run task on T1 +run task on T3 +run task on T2 done +run task on T1 done +run task on T3 done +Entry state T2 +Entry state T1 +Entry state T3 +Entry state T1E +Entry state T2E +Entry state T3E +Exit state TASKS +Entry state JOIN +Exit state JOIN +Entry state ERROR +Entry state AUTOMATIC +Exit state AUTOMATIC +Entry state MANUAL + +sm>tasks fix +Exit state MANUAL +Exit state ERROR +Entry state READY +---- +==== + +In the precding example, if we simulate failure for either task `T2` or `T3`, the state +machine goes to the `MANUAL` state, where problem needs to be fixed manually +before it can go back to the `READY` state. diff --git a/docs/src/reference/asciidoc/sm-examples-turnstile.adoc b/docs/src/reference/asciidoc/sm-examples-turnstile.adoc new file mode 100644 index 00000000..134b3f7e --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-turnstile.adoc @@ -0,0 +1,85 @@ +[[statemachine-examples-turnstile]] +== Turnstile + +Turnstile is a simple device that gives you access if payment is +made. It is a concept that is simple to model using a state machine. In its +simplest, form there are only two states: `LOCKED` and `UNLOCKED`. Two +events, `COIN` and `PUSH` can happen, depending on whether someone +makes a payment or tries to go through the turnstile. +The following image shows the state machine: + +image::images/statechart1.png[width=500] + +The following listing shows the enumeration that defines the possible states: + +==== +.States +[source,java,indent=0] +---- +include::samples/demo/turnstile/Application.java[tags=snippetB] +---- +==== + +The following listing shows the enumeration that defines the events: + +==== +.Events +[source,java,indent=0] +---- +include::samples/demo/turnstile/Application.java[tags=snippetC] +---- +==== + +The following listing shows the code that configures the state machine: + +==== +.Configuration +[source,java,indent=0] +---- +include::samples/demo/turnstile/Application.java[tags=snippetA] +---- +==== + +You can see how this sample state machine interacts with events by +running the `turnstile` sample. The following listing shows how to do so +and shows the command's output: + +==== +[source,text,subs="verbatim,attributes"] +---- +$ java -jar spring-statemachine-samples-turnstile-{revnumber}.jar + +sm>sm print ++----------------------------------------------------------------+ +| SM | ++----------------------------------------------------------------+ +| | +| +----------------+ +----------------+ | +| *-->| LOCKED | | UNLOCKED | | +| +----------------+ +----------------+ | +| +---| entry/ | | entry/ |---+ | +| | | exit/ | | exit/ | | | +| | | | | | | | +| PUSH| | |---COIN-->| | |COIN | +| | | | | | | | +| | | | | | | | +| | | |<--PUSH---| | | | +| +-->| | | |<--+ | +| | | | | | +| +----------------+ +----------------+ | +| | ++----------------------------------------------------------------+ + +sm>sm start +State changed to LOCKED +State machine started + +sm>sm event COIN +State changed to UNLOCKED +Event COIN send + +sm>sm event PUSH +State changed to LOCKED +Event PUSH send +---- +==== diff --git a/docs/src/reference/asciidoc/sm-examples-washer.adoc b/docs/src/reference/asciidoc/sm-examples-washer.adoc new file mode 100644 index 00000000..495fb4cd --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-washer.adoc @@ -0,0 +1,98 @@ +[[statemachine-examples-washer]] +== Washer + +The washer sample demonstrates how to use a history state to recover a +running state configuration with a simulated power-off situation. + +Anyone who has ever used a washing machine knows that if you somehow pause +the program, it continue from the same state when unpaused. +You can implement this kind of behavior in a state machine by using +a history pseudo state. +The following image shows our state machine for a washer: + +image::images/statechart6.png[width=500] + +The following listing shows the enumeration that defines the possible states: + +==== +.States +[source,java,indent=0] +---- +include::samples/demo/washer/Application.java[tags=snippetB] +---- +==== + +The following listing shows the enumeration that defines the events: + +==== +.Events +[source,java,indent=0] +---- +include::samples/demo/washer/Application.java[tags=snippetC] +---- +==== + +The following listing configures the possible states: + +==== +.Configuration - states +[source,java,indent=0] +---- +include::samples/demo/washer/Application.java[tags=snippetAA] +---- +==== + +The following listing configures the possible transitions: + +==== +.Configuration - transitions +[source,java,indent=0] +---- +include::samples/demo/washer/Application.java[tags=snippetAB] +---- +==== + +The following example shows how this state machine actually works: + +==== +[source,text] +---- +sm>sm start +Entry state RUNNING +Entry state WASHING +State machine started + +sm>sm event RINSE +Exit state WASHING +Entry state RINSING +Event RINSE send + +sm>sm event DRY +Exit state RINSING +Entry state DRYING +Event DRY send + +sm>sm event CUTPOWER +Exit state DRYING +Exit state RUNNING +Entry state POWEROFF +Event CUTPOWER send + +sm>sm event RESTOREPOWER +Exit state POWEROFF +Entry state RUNNING +Entry state WASHING +Entry state DRYING +Event RESTOREPOWER send +---- +==== + +In the preceding run: + +* The state machine is started, which causes machine to get initialized. +* The state machine goes to RINSING state. +* The state machine goes to DRYING state. +* The state machine cuts power and goes to POWEROFF state. +* The state is restored from the HISTORY state, which takes state machine back + to its previous known state. + diff --git a/docs/src/reference/asciidoc/sm-examples-web.adoc b/docs/src/reference/asciidoc/sm-examples-web.adoc new file mode 100644 index 00000000..07e982f1 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-web.adoc @@ -0,0 +1,64 @@ +[[statemachine-examples-web]] +== Web + +Web is a distributed state machine example that uses a zookeeper state machine to handle +distributed state. See <>. + +NOTE: This example is meant to be run on multiple +browser sessions against multiple different hosts. + +This sample uses a modified state machine structure from +<> to work with a distributed state +machine. The following image shows the state machine logic: + +image::images/statechart11.png[width=500] + +NOTE: Due to the nature of this sample, an instance of a `Zookeeper` state machine is expected to +be available from a localhost for every individual sample instance. + +This demonstration uses an example that starts three different sample instances. +If you run different instances on the same host, you need to +distinguish the port each one uses by adding `--server.port=` to the command. +Otherwise the default port for each host is `8080`. + +In this sample run, we have three hosts: `n1`, `n2`, and `n3`. Each one +has a local zookeeper instance running and a state machine sample running +on a port `8080`. + +In there different terminals, start the three different state machines by running +the following command: + +==== +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-web-{revnumber}.jar +---- +==== + +When all instances are running, you should see that all show similar +information when you access them with a browser. The states should be `S0`, `S1`, and `S11`. +The extended state variable named `foo` should have a value of `0`. The main state is `S11`. + +image::images/sm-dist-n1-1.png[width=500] + +When you press the `Event C` button in any of the browser windows, the +distributed state is changed to `S211,` which is the target state +denoted by the transition associated with an event of type `C`. +The following image shows the change: + +image::images/sm-dist-n2-2.png[width=500] + +Now we can press the `Event H` button and see that the +internal transition runs on all state machines to change the +the value of the extended state variable named `foo` from `0` to `1`. This change is +first done on the state machine that receives the event and is then propagated +to the other state machines. You should see only the variable named `foo` change +from `0` to `1`. + +image::images/sm-dist-n3-3.png[width=500] + +Finally, we can send `Event K`, which takes the state +machine state back to state `S11`. You should see this happen in +all of the browsers. The following image shows the result in one browser: + +image::images/sm-dist-n1-4.png[width=500] diff --git a/docs/src/reference/asciidoc/sm-examples-zookeeper.adoc b/docs/src/reference/asciidoc/sm-examples-zookeeper.adoc new file mode 100644 index 00000000..0326ed40 --- /dev/null +++ b/docs/src/reference/asciidoc/sm-examples-zookeeper.adoc @@ -0,0 +1,111 @@ +[[statemachine-examples-zookeeper]] +== Zookeeper + +Zookeeper is a distributed version from the +<> sample. + +NOTE: This sample needs an external `Zookeeper` instance that is accessible from +`localhost` and has the default port and settings. + +Configuration of this sample is almost the same as the `turnstile` sample. We +add only the configuration for the distributed state machine where we +configure `StateMachineEnsemble`, as the following listing shows: + +==== +[source,java,indent=0] +---- +include::samples/demo/zookeeper/Application.java[tags=snippetA] +---- +==== + +The actual `StateMachineEnsemble` needs to be created as a bean, together +with the `CuratorFramework` client, as the following example shows: + +==== +[source,java,indent=0] +---- +include::samples/demo/zookeeper/Application.java[tags=snippetB] +---- +==== + +For the next example, we need to create two different shell instances. +We need to create one instance, see what happens, and then create the second instance. +The following command starts the shell instances (remember to start only one instance for now): + +==== +[source,text,subs="attributes"] +---- +@n1:~# java -jar spring-statemachine-samples-zookeeper-{revnumber}.jar +---- +==== + + +When state machine is started, its initial state is +`LOCKED`. Then it sends a `COIN` event to transition into `UNLOCKED` state. +The following example shows what happens: + +==== +.Shell1 +[source,text] +---- +sm>sm start +Entry state LOCKED +State machine started + +sm>sm event COIN +Exit state LOCKED +Entry state UNLOCKED +Event COIN send + +sm>sm state +UNLOCKED +---- +==== + +Now you can open a second shell instance and start a state machine, +by using the same command that you used to start the first state machine. You should see +that the distributed state (`UNLOCKED`) is entered instead of the default +initial state (`LOCKED`). + +The following example shows the state machine and its output: + +==== +.Shell2 +[source,text] +---- +sm>sm start +State machine started + +sm>sm state +UNLOCKED +---- +==== + +Then from either shell (we use second instance in the next example), send a +`PUSH` event to transit from the `UNLOCKED` into the `LOCKED` state. +The following example shows the state machine command and its output: + +==== +.Shell2 +[source,text] +---- +sm>sm event PUSH +Exit state UNLOCKED +Entry state LOCKED +Event PUSH send +---- +==== + +In the other shell (the first shell if you ran the preceding command in the second shell), +you should see the state be changed automatically, +based on distributed state kept in Zookeeper. +The following example shows the state machine command and its output: + +==== +.Shell1 +[source,text] +---- +sm>Exit state UNLOCKED +Entry state LOCKED +---- +==== diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 08455b20..23d9faa1 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -62,2102 +62,21 @@ NOTE: The filenames for the jars to which we refer in this section are populated build of this document, meaning that, if you build samples from master, you have files with a `BUILD-SNAPSHOT` postfix. -[[statemachine-examples-turnstile]] -== Turnstile - -Turnstile is a simple device that gives you access if payment is -made. It is a concept that is simple to model using a state machine. In its -simplest, form there are only two states: `LOCKED` and `UNLOCKED`. Two -events, `COIN` and `PUSH` can happen, depending on whether someone -makes a payment or tries to go through the turnstile. -The following image shows the state machine: - -image::images/statechart1.png[width=500] - -The following listing shows the enumeration that defines the possible states: - -==== -.States -[source,java,indent=0] ----- -include::samples/demo/turnstile/Application.java[tags=snippetB] ----- -==== - -The following listing shows the enumeration that defines the events: - -==== -.Events -[source,java,indent=0] ----- -include::samples/demo/turnstile/Application.java[tags=snippetC] ----- -==== - -The following listing shows the code that configures the state machine: - -==== -.Configuration -[source,java,indent=0] ----- -include::samples/demo/turnstile/Application.java[tags=snippetA] ----- -==== - -You can see how this sample state machine interacts with events by -running the `turnstile` sample. The following listing shows how to do so -and shows the command's output: - -==== -[source,text,subs="verbatim,attributes"] ----- -$ java -jar spring-statemachine-samples-turnstile-{revnumber}.jar - -sm>sm print -+----------------------------------------------------------------+ -| SM | -+----------------------------------------------------------------+ -| | -| +----------------+ +----------------+ | -| *-->| LOCKED | | UNLOCKED | | -| +----------------+ +----------------+ | -| +---| entry/ | | entry/ |---+ | -| | | exit/ | | exit/ | | | -| | | | | | | | -| PUSH| | |---COIN-->| | |COIN | -| | | | | | | | -| | | | | | | | -| | | |<--PUSH---| | | | -| +-->| | | |<--+ | -| | | | | | -| +----------------+ +----------------+ | -| | -+----------------------------------------------------------------+ - -sm>sm start -State changed to LOCKED -State machine started - -sm>sm event COIN -State changed to UNLOCKED -Event COIN send - -sm>sm event PUSH -State changed to LOCKED -Event PUSH send ----- -==== - -[[statemachine-examples-showcase]] -== Showcase - -Showcase is a complex state machine that shows all possible transition -topologies up to four levels of state nesting. -The following image shows the state machine: - -image::images/statechart2.png[width=500] - -The following listing shows the enumeration that defines the possible states: - -==== -.States -[source,java,indent=0] ----- -include::samples/demo/showcase/Application.java[tags=snippetB] ----- -==== - -The following listing shows the enumeration that defines the events: - -==== -.Events -[source,java,indent=0] ----- -include::samples/demo/showcase/Application.java[tags=snippetC] ----- -==== - -The following listing shows the code that configures the state machine: - -==== -.Configuration - states -[source,java,indent=0] ----- -include::samples/demo/showcase/Application.java[tags=snippetAA] ----- -==== - -The following listing shows the code that configures the state machine's transitions: - -==== -.Configuration - transitions -[source,java,indent=0] ----- -include::samples/demo/showcase/Application.java[tags=snippetAB] ----- -==== - -The following listing shows the code that configures the state machine's actions and guards: - -==== -.Configuration - actions and guards -[source,java,indent=0] ----- -include::samples/demo/showcase/Application.java[tags=snippetAC] ----- -==== - -The following listing shows how the single action is defined: - -==== -.Action -[source,java,indent=0] ----- -include::samples/demo/showcase/Application.java[tags=snippetD] ----- -==== - -The following listing shows how the single guard is defined: - -==== -.Guard -[source,java,indent=0] ----- -include::samples/demo/showcase/Application.java[tags=snippetE] ----- -==== - -The following listing shows the output that this state machine produces when it runs and -various events are sent to it: - -==== -[source,text] ----- -sm>sm start -Init foo to 0 -Entry state S0 -Entry state S1 -Entry state S11 -State machine started - -sm>sm event A -Event A send - -sm>sm event C -Exit state S11 -Exit state S1 -Entry state S2 -Entry state S21 -Entry state S211 -Event C send - -sm>sm event H -Switch foo to 1 -Internal transition source=S0 -Event H send - -sm>sm event C -Exit state S211 -Exit state S21 -Exit state S2 -Entry state S1 -Entry state S11 -Event C send - -sm>sm event A -Exit state S11 -Exit state S1 -Entry state S1 -Entry state S11 -Event A send ----- -==== - -In the preceding output, we can see that: - -* The state machine is started, which takes it to its initial state (`S11`) - through superstates (`S1`) and (`S0`). Also, the extended state variable, `foo`, is - initialized to `0`. -* We try to execute a self transition in state `S1` with event `A`, but - nothing happens because the transition is guarded by variable `foo` to - be `1`. -* We send event `C`, which takes us to the other state machine, where - the initial state (`S211`) and its superstates are entered. In there, we - can use event `H`, which does a simple internal transition to flip the - `foo` variable. Then we go back by using event `C`. -* Event `A` is sent again, and now `S1` does a self transition because the - guard evaluates to `true`. - -The following example offers a closer look at how hierarchical states and their event -handling works: - -==== -[source,text] ----- -sm>sm variables -No variables - -sm>sm start -Init foo to 0 -Entry state S0 -Entry state S1 -Entry state S11 -State machine started - -sm>sm variables -foo=0 - -sm>sm event H -Internal transition source=S1 -Event H send - -sm>sm variables -foo=0 - -sm>sm event C -Exit state S11 -Exit state S1 -Entry state S2 -Entry state S21 -Entry state S211 -Event C send - -sm>sm variables -foo=0 - -sm>sm event H -Switch foo to 1 -Internal transition source=S0 -Event H send - -sm>sm variables -foo=1 - -sm>sm event H -Switch foo to 0 -Internal transition source=S2 -Event H send - -sm>sm variables -foo=0 ----- -==== - -In the preceding sample: - -* We print extended state variables in various stages. -* With event `H`, we end up running an internal transition, - which is logged with its source state. -* Note how event `H` is handled in - different states (`S0`, `S1`, and `S2`). This is a good example of how - hierarchical states and their event handling works. If state `S2` is - unable to handle event `H` due to a guard condition, its parent is - checked next. This guarantees that, while the machine is on state `S2`, the `foo` flag - is always flipped around. However, in state `S1`, event `H` always - matches to its dummy transition without guard or action, so it never - happens. - -[[statemachine-examples-cdplayer]] -== CD Player - -CD Player is a sample which resembles a use case that many people have -used in the real world. CD Player itself is a really simple entity that allows a -user to open a deck, insert or change a disk, and then drive the player's -functionality by pressing various buttons (`eject`, `play`, -`stop`, `pause`, `rewind`, and `backward`). - -How many of us have really given thought to what it will take to -make code that interacts with hardware to drive a CD Player. Yes, the -concept of a player is simple, but, if you look behind the scenes, -things actually get a bit convoluted. - -You have probably noticed that, if your deck is open and you press play, the -deck closes and a song starts to play (if a CD was inserted). -In a sense, when the deck is open, you first need to close -it and then try to start playing (again, if a CD is actually inserted). Hopefully, -you have now realized that a simple CD Player is so simple. -Sure, you can wrap all this with a simple class that has a few boolean variables -and probably a few nested if-else clauses. That will do the job, but what -about if you need to make all this behavior much more complex? Do you -really want to keep adding more flags and if-else clauses? - -The following image shows the state machine for our simple CD player: - -image::images/statechart3.png[width=500] - -The rest of this section goes through how this sample and its state machine is designed and -how those two interacts with each other. The following three configuration sections -are used within an `EnumStateMachineConfigurerAdapter`. - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetAA] ----- - -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetAB] ----- - -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetAC] ----- -==== - -In the preceding configuration: - -* We used `EnumStateMachineConfigurerAdapter` to configure states and - transitions. -* The `CLOSED` and `OPEN` states are defined as substates of `IDLE`, and - the `PLAYING` and `PAUSED` states are defined as substates of `BUSY`. -* With the `CLOSED` state, we added an entry action as a bean called - `closedEntryAction`. -* In the transitions we mostly map events to expected state - transitions, such as `EJECT` closing and opening a deck and `PLAY`, `STOP`, - and `PAUSE` doing their natural transitions. For other transitions, we did the following: -** For source state `PLAYING`, we added a timer trigger, which is -needed to automatically track elapsed time within a playing track and -to have a facility for making the decision about when to switch the to next track. -** For the `PLAY` event, if the source state is `IDLE` and the target state is - `BUSY`, we defined an action called `playAction` and a guard called `playGuard`. -** For the `LOAD` event and the `OPEN` state, we defined an internal -transition with an action called `loadAction`, which tracks inserting a disc with -extended-state variables. -** The `PLAYING` state defines three internal transitions. One is -triggered by a timer that runs an action called `playingAction`, which updates -the extended state variables. The other two transitions use `trackAction` -with different events (`BACK` and `FORWARD`, respectively) to handle -when the user wants to go back or forward in tracks. - -This machine has only have six states, which are defined by the following enumeration: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetB] ----- -==== - -Events represent the buttons the user can -press and whether the user loads a disc into the player. -The following enumeration defines the events: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetC] ----- -==== - -The `cdPlayer` and `library` beans are used to drive the application. -The following listing shows the definition of these two beans: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetD] ----- -==== - - -We define extended state variable keys as simple enumerations, -as the following listing shows: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetE] ----- -==== - -We wanted to make this sample type safe, so we define our own -annotation (`@StatesOnTransition`), which has a mandatory meta -annotation (`@OnTransition`). -The following listing defines the `@StatesOnTransition` annotation: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetF] ----- -==== - -`ClosedEntryAction` is an entry action for the `CLOSED` state, to -send a `PLAY` event to the state machine if a disc is present. -The following listing defines `ClosedEntryAction`: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetG] ----- -==== - -`LoadAction` update an extended state variable if event -headers contain information about a disc to load. -The following listing defines `LoadAction`: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetH] ----- -==== - -`PlayAction` resets the player's elapsed time, which is kept as -an extended state variable. -The following listing defines `PlayAction`: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetI] ----- -==== - -`PlayGuard` guards the transition from `IDLE` to `BUSY` with the -`PLAY` event if the `CD` extended state variable does not indicate that a -disc has been loaded. -The following listing defines `PlayGuard`: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetJ] ----- -==== - -`PlayingAction` updates an extended state variable called `ELAPSEDTIME`, which -the player can use to read and update its LCD status display. `PlayingAction` also handles -track shifting when the user goe back or forward in tracks. -The following example defines `PlayingAction`: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetK] ----- -==== - -`TrackAction` handles track shift actions when the user goes back or forward -in tracks. If a track is the last on a disc, playing is stopped and the `STOP` -event is sent to a state machine. -The following example defines `TrackAction`: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/Application.java[tags=snippetL] ----- -==== - -One other important aspect of state machines is that they have their -own responsibilities (mostly around handling states) and that all application -level logic should be kept outside. This means that applications need -to have a ways to interact with a state machine. Also, note -that we annotated `CdPlayer` with `@WithStateMachine`, which instructs a -state machine to find methods from your POJO, which are then called -with various transitions. -The following example shows how it updates its LCD status display: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/CdPlayer.java[tags=snippetA] ----- -==== - -In the preceding example, we use the `@OnTransition` annotation to hook a callback -when a transition happens with a target state of `BUSY`. - -The following listing shows how our state machine handles whether the player is closed: - -==== -[source,java,indent=0] ----- -include::samples/demo/cdplayer/CdPlayer.java[tags=snippetB] ----- -==== - -`@OnTransition` (which we used in the preceding examples) can only be -used with strings that are matched from enumerations. `@StatesOnTransition` -lets you create your own type-safe annotations that use real enumerations. - -The following example shows how this state machine actually works. - -==== -[source,text] ----- -sm>sm start -Entry state IDLE -Entry state CLOSED -State machine started - -sm>cd lcd -No CD - -sm>cd library -0: Greatest Hits - 0: Bohemian Rhapsody 05:56 - 1: Another One Bites the Dust 03:36 -1: Greatest Hits II - 0: A Kind of Magic 04:22 - 1: Under Pressure 04:08 - -sm>cd eject -Exit state CLOSED -Entry state OPEN - -sm>cd load 0 -Loading cd Greatest Hits - -sm>cd play -Exit state OPEN -Entry state CLOSED -Exit state CLOSED -Exit state IDLE -Entry state BUSY -Entry state PLAYING - -sm>cd lcd -Greatest Hits Bohemian Rhapsody 00:03 - -sm>cd forward - -sm>cd lcd -Greatest Hits Another One Bites the Dust 00:04 - -sm>cd stop -Exit state PLAYING -Exit state BUSY -Entry state IDLE -Entry state CLOSED - -sm>cd lcd -Greatest Hits ----- -==== - -In the preceding run: - -* The state machine is started, which causes the machine to be initialized. -* The CD player's LCD screen status is printed. -* The CD library is printed. -* The CD player's deck is opened. -* The CD with index 0 is loaded into a deck. -* Play causes the deck to get closed and immediate play, because a disc - was inserted. -* We print the LCD status and request the next track. -* We stop playing. - -[[statemachine-examples-tasks]] -== Tasks - -The Tasks sample demonstrates parallel task handling within -regions and adds error handling to either -automatically or manually fix task problems before continuing back -to a state where the tasks can be run again. -The following image shows the Tasks state machine: - -image::images/statechart5.png[width=500] - -On a high level, in this state machine: - -* We always try to get into the `READY` state so that we can use the - RUN event to execute tasks. -* Tkhe `TASKS` state, which is composed of three independent regions, has been - put in the middle of `FORK` and `JOIN` states, which will cause the regions to - go into their initial states and to be joined by their end states. -* From the `JOIN` state, we automatically go into a `CHOICE` state, which checks - for the existence of error flags in extended state variables. Tasks can set - these flags, and doing so gives the `CHOICE` state the ability to go into the `ERROR` - state, where errors can be handled either automatically or manually. -* The `AUTOMATIC` state in `ERROR` can try to automatically fix an error and goes - back to `READY` if it succeeds. If the error is something what - cannot be handled automatically, user intervention is needed and the - machine is put into the `MANUAL` state by the `FALLBACK` event. - -The following listing shows the enumeration that defines the possible states: - -==== -.States -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetB] ----- -==== - -The following listing shows the enumeration that defines the events: - -==== -.Events -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetC] ----- -==== - -The following listing configures the possible states: - -==== -.Configuration - states -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetAA] ----- -==== - -The following listing configures the possible transitions: - -==== -.Configuration - transitions -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetAB] ----- -==== - -The following guard sends a choice entry into the `ERROR` state and needs to -return `TRUE` if an error has happened. This guard checks that -all extended state variables(`T1`, `T2`, and `T3`) are `TRUE`. - -==== -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetAC] ----- -==== - -The following actions below send events to the state machine to request -the next step, which is either to fall back or to continue back to ready. - -==== -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetAD] ----- -==== - -Currently, the default region execution is synchronous, but you can change -it to be asynchronous by changing `TaskExecutor`. Task simulates -work by sleeping two seconds so that you can see how actions in -regions are executed inparallel. -The following listing shows the `TaskExecutor` bean definition: - -==== -[source,java,indent=0] ----- -include::samples/demo/tasks/Application.java[tags=snippetAE] ----- -==== - -The following example shows how this state machine actually works: - -==== -[source,text] ----- -sm>sm start -State machine started -Entry state READY - -sm>tasks run -Entry state TASKS -run task on T3 -run task on T2 -run task on T1 -run task on T2 done -run task on T1 done -run task on T3 done -Entry state T2 -Entry state T3 -Entry state T1 -Entry state T1E -Entry state T2E -Entry state T3E -Exit state TASKS -Entry state JOIN -Exit state JOIN -Entry state READY ----- -==== - -In the preceding listing, we can see that tasks run multiple times. -In the next listing, we introduce errors: - -==== -[source,text] ----- -sm>tasks list -Tasks {T1=true, T3=true, T2=true} - -sm>tasks fail T1 - -sm>tasks list -Tasks {T1=false, T3=true, T2=true} - -sm>tasks run -Entry state TASKS -run task on T1 -run task on T3 -run task on T2 -run task on T1 done -run task on T3 done -run task on T2 done -Entry state T1 -Entry state T3 -Entry state T2 -Entry state T1E -Entry state T2E -Entry state T3E -Exit state TASKS -Entry state JOIN -Exit state JOIN -Entry state ERROR -Entry state AUTOMATIC -Exit state AUTOMATIC -Exit state ERROR -Entry state READY ----- -==== - -In the preceding listing, if we simulate a failure for task T1, it is fixed -automatically. -In the next listing, we introduce more errors: - -==== -[source,text] ----- -sm>tasks list -Tasks {T1=true, T3=true, T2=true} - -sm>tasks fail T2 - -sm>tasks run -Entry state TASKS -run task on T2 -run task on T1 -run task on T3 -run task on T2 done -run task on T1 done -run task on T3 done -Entry state T2 -Entry state T1 -Entry state T3 -Entry state T1E -Entry state T2E -Entry state T3E -Exit state TASKS -Entry state JOIN -Exit state JOIN -Entry state ERROR -Entry state AUTOMATIC -Exit state AUTOMATIC -Entry state MANUAL - -sm>tasks fix -Exit state MANUAL -Exit state ERROR -Entry state READY ----- -==== - -In the precding example, if we simulate failure for either task `T2` or `T3`, the state -machine goes to the `MANUAL` state, where problem needs to be fixed manually -before it can go back to the `READY` state. - -[[statemachine-examples-washer]] -== Washer - -The washer sample demonstrates how to use a history state to recover a -running state configuration with a simulated power-off situation. - -Anyone who has ever used a washing machine knows that if you somehow pause -the program, it continue from the same state when unpaused. -You can implement this kind of behavior in a state machine by using -a history pseudo state. -The following image shows our state machine for a washer: - -image::images/statechart6.png[width=500] - -The following listing shows the enumeration that defines the possible states: - -==== -.States -[source,java,indent=0] ----- -include::samples/demo/washer/Application.java[tags=snippetB] ----- -==== - -The following listing shows the enumeration that defines the events: - -==== -.Events -[source,java,indent=0] ----- -include::samples/demo/washer/Application.java[tags=snippetC] ----- -==== - -The following listing configures the possible states: - -==== -.Configuration - states -[source,java,indent=0] ----- -include::samples/demo/washer/Application.java[tags=snippetAA] ----- -==== - -The following listing configures the possible transitions: - -==== -.Configuration - transitions -[source,java,indent=0] ----- -include::samples/demo/washer/Application.java[tags=snippetAB] ----- -==== - -The following example shows how this state machine actually works: - -==== -[source,text] ----- -sm>sm start -Entry state RUNNING -Entry state WASHING -State machine started - -sm>sm event RINSE -Exit state WASHING -Entry state RINSING -Event RINSE send - -sm>sm event DRY -Exit state RINSING -Entry state DRYING -Event DRY send - -sm>sm event CUTPOWER -Exit state DRYING -Exit state RUNNING -Entry state POWEROFF -Event CUTPOWER send - -sm>sm event RESTOREPOWER -Exit state POWEROFF -Entry state RUNNING -Entry state WASHING -Entry state DRYING -Event RESTOREPOWER send ----- -==== - -In the preceding run: - -* The state machine is started, which causes machine to get initialized. -* The state machine goes to RINSING state. -* The state machine goes to DRYING state. -* The state machine cuts power and goes to POWEROFF state. -* The state is restored from the HISTORY state, which takes state machine back - to its previous known state. - -[[statemachine-examples-persist]] -== Persist - -Persist is a sample that uses the <> recipe to -demonstrate how database entry update logic can be controlled by a -state machine. - -The following image shows the state machine logic and configuration: - -image::images/statechart10.png[width=500] - -The following listing shows the state machine configuration: - -==== -.StateMachine Config -[source,java,indent=0] ----- -include::samples/demo/persist/Application.java[tags=snippetA] ----- -==== - -The following configuration creates `PersistStateMachineHandler`: - -==== -.Handler Config -[source,java,indent=0] ----- -include::samples/demo/persist/Application.java[tags=snippetB] ----- -==== - -The following listing shows the `Order` class used with this sample: - -==== -.Order Class -[source,java,indent=0] ----- -include::samples/demo/persist/Application.java[tags=snippetC] ----- -==== - -The following example shows the state machine's output: - -==== -[source,text] ----- -sm>persist db -Order [id=1, state=PLACED] -Order [id=2, state=PROCESSING] -Order [id=3, state=SENT] -Order [id=4, state=DELIVERED] - -sm>persist process 1 -Exit state PLACED -Entry state PROCESSING - -sm>persist db -Order [id=2, state=PROCESSING] -Order [id=3, state=SENT] -Order [id=4, state=DELIVERED] -Order [id=1, state=PROCESSING] - -sm>persist deliver 3 -Exit state SENT -Entry state DELIVERED - -sm>persist db -Order [id=2, state=PROCESSING] -Order [id=4, state=DELIVERED] -Order [id=1, state=PROCESSING] -Order [id=3, state=DELIVERED] ----- -==== - -In the preceding run, the state machine: - -* Listed rows from an existing embedded database, which is already - populated with sample data. -* Requested to update order `1` into the `PROCESSING` state. -* List database entries again and see that the state has been changed from - `PLACED` to `PROCESSING`. -* Update order `3` to update its state from `SENT` to - `DELIVERED`. - -[NOTE] -==== -You may wonder where the database is, because there are literally no -signs of it in the sample code. The sample is based on Spring Boot and, -because the necessary classes are in a classpath, an embedded `HSQL` instance -is created automatically. - -Spring Boot even creates an instance of `JdbcTemplate`, which you -can autowire, as we did in `Persist.java`, shown in the following listing: - -===== -[source,java,indent=0] ----- -include::samples/demo/persist/Persist.java[tags=snippetA] ----- -===== -==== - -Next, we need to handle state changes. The following listing shows how we do so: - -==== -[source,java,indent=0] ----- -include::samples/demo/persist/Persist.java[tags=snippetB] ----- -==== - -Finally, we use a `PersistStateChangeListener` to update the database, as the -following listing shows: - -==== -[source,java,indent=0] ----- -include::samples/demo/persist/Persist.java[tags=snippetC] ----- -==== - -[[statemachine-examples-zookeeper]] -== Zookeeper - -Zookeeper is a distributed version from the -<> sample. - -NOTE: This sample needs an external `Zookeeper` instance that is accessible from -`localhost` and has the default port and settings. - -Configuration of this sample is almost the same as the `turnstile` sample. We -add only the configuration for the distributed state machine where we -configure `StateMachineEnsemble`, as the following listing shows: - -==== -[source,java,indent=0] ----- -include::samples/demo/zookeeper/Application.java[tags=snippetA] ----- -==== - -The actual `StateMachineEnsemble` needs to be created as a bean, together -with the `CuratorFramework` client, as the following example shows: - -==== -[source,java,indent=0] ----- -include::samples/demo/zookeeper/Application.java[tags=snippetB] ----- -==== - -For the next example, we need to create two different shell instances. -We need to create one instance, see what happens, and then create the second instance. -The following command starts the shell instances (remember to start only one instance for now): - -==== -[source,text,subs="attributes"] ----- -@n1:~# java -jar spring-statemachine-samples-zookeeper-{revnumber}.jar ----- -==== - - -When state machine is started, its initial state is -`LOCKED`. Then it sends a `COIN` event to transition into `UNLOCKED` state. -The following example shows what happens: - -==== -.Shell1 -[source,text] ----- -sm>sm start -Entry state LOCKED -State machine started - -sm>sm event COIN -Exit state LOCKED -Entry state UNLOCKED -Event COIN send - -sm>sm state -UNLOCKED ----- -==== - -Now you can open a second shell instance and start a state machine, -by using the same command that you used to start the first state machine. You should see -that the distributed state (`UNLOCKED`) is entered instead of the default -initial state (`LOCKED`). - -The following example shows the state machine and its output: - -==== -.Shell2 -[source,text] ----- -sm>sm start -State machine started - -sm>sm state -UNLOCKED ----- -==== - -Then from either shell (we use second instance in the next example), send a -`PUSH` event to transit from the `UNLOCKED` into the `LOCKED` state. -The following example shows the state machine command and its output: - -==== -.Shell2 -[source,text] ----- -sm>sm event PUSH -Exit state UNLOCKED -Entry state LOCKED -Event PUSH send ----- -==== - -In the other shell (the first shell if you ran the preceding command in the second shell), -you should see the state be changed automatically, -based on distributed state kept in Zookeeper. -The following example shows the state machine command and its output: - -==== -.Shell1 -[source,text] ----- -sm>Exit state UNLOCKED -Entry state LOCKED ----- -==== - -[[statemachine-examples-web]] -== Web - -Web is a distributed state machine example that uses a zookeeper state machine to handle -distributed state. See <>. - -NOTE: This example is meant to be run on multiple -browser sessions against multiple different hosts. - -This sample uses a modified state machine structure from -<> to work with a distributed state -machine. The following image shows the state machine logic: - -image::images/statechart11.png[width=500] - -NOTE: Due to the nature of this sample, an instance of a `Zookeeper` state machine is expected to -be available from a localhost for every individual sample instance. - -This demonstration uses an example that starts three different sample instances. -If you run different instances on the same host, you need to -distinguish the port each one uses by adding `--server.port=` to the command. -Otherwise the default port for each host is `8080`. - -In this sample run, we have three hosts: `n1`, `n2`, and `n3`. Each one -has a local zookeeper instance running and a state machine sample running -on a port `8080`. - -In there different terminals, start the three different state machines by running -the following command: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-web-{revnumber}.jar ----- -==== - -When all instances are running, you should see that all show similar -information when you access them with a browser. The states should be `S0`, `S1`, and `S11`. -The extended state variable named `foo` should have a value of `0`. The main state is `S11`. - -image::images/sm-dist-n1-1.png[width=500] - -When you press the `Event C` button in any of the browser windows, the -distributed state is changed to `S211,` which is the target state -denoted by the transition associated with an event of type `C`. -The following image shows the change: - -image::images/sm-dist-n2-2.png[width=500] - -Now we can press the `Event H` button and see that the -internal transition runs on all state machines to change the -the value of the extended state variable named `foo` from `0` to `1`. This change is -first done on the state machine that receives the event and is then propagated -to the other state machines. You should see only the variable named `foo` change -from `0` to `1`. - -image::images/sm-dist-n3-3.png[width=500] - -Finally, we can send `Event K`, which takes the state -machine state back to state `S11`. You should see this happen in -all of the browsers. The following image shows the result in one browser: - -image::images/sm-dist-n1-4.png[width=500] - -[[statemachine-examples-scope]] - -== Scope - -Scope is a state machine example that uses session scope to provide an -individual instance for every user. -The following image shows the states and events within the Scope state machine: - -image::images/statechart12.png[width=500] - -This simple state machine has three states: `S0`, `S1`, and `S2`. -Transitions between those are controlled by three events: `A`, `B`, and `C`. - -To start the state machine, run the following command in a terminal: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-scope-{revnumber}.jar ----- -==== - -When the instance is running, you can open a browser and play with the state -machine. If you open the same page in a different browser, (for example, one in -Chrome and one in Firefox), you should get a new state machine -instance for each user session. -The following image shows the state machine in a browser: - -image::images/sm-scope-1.png[width=500] - -[[statemachine-examples-security]] -== Security - -Security is a state machine example that uses most of the possible combinations of -securing a state machine. It secures sending events, transitions, -and actions. -The following image shows the state machine's states and events: - -image::images/statechart13.png[width=500] - -To start the state machine, run the following command: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-secure-{revnumber}.jar ----- -==== - -We secure event sending by requiring that users have a role of `USER`. -Spring Security ensures that no other users can send events to this -state machine. -The following listing secures event sending: - -==== -[source,java,indent=0] ----- -include::samples/demo/security/StateMachineConfig.java[tags=snippetA] ----- -==== - -In this sample we define two users: - -* A user named `user` who has a role of `USER` -* A user named `admin` who has two roles: `USER` and `ADMIN` - -The password for both users is `password`. -The following listing configures the two users: - -==== -[source,java,indent=0] ----- -include::samples/demo/security/StateMachineConfig.java[tags=snippetE] ----- -==== - -We define various transitions between states according to the state chart -shown at the beginning of the example. Only a user with an active `ADMIN` role can run -the external transitions between `S2` and `S3`. Similarly only an `ADMIN` can -run the internal transition the `S1` state. -The following listing defines the transitions, including their security: - -==== -[source,java,indent=0] ----- -include::samples/demo/security/StateMachineConfig.java[tags=snippetB] ----- -==== - -The following listing uses a method called `adminAction` whose return type is `Action` to -specify that the action is secured with a role of `ADMIN`: - -==== -[source,java,indent=0] ----- -include::samples/demo/security/StateMachineConfig.java[tags=snippetC] ----- -==== - -The following `Action` runs an internal transition in state `S` when event `F` is sent. - -==== -[source,java,indent=0] ----- -include::samples/demo/security/StateMachineConfig.java[tags=snippetD] ----- -==== - -The transition itself is secured with a -role of `ADMIN`, so this transition does not run if the current user -does not hate that role. - -[[statemachine-examples-eventservice]] -== Event Service - -The event service example shows how you can use state machine concepts as -a processing engine for events. This sample evolved from a question: - -Can I use Spring Statemachine as a microservice to feed events to -different state machine instances? In fact, Spring Statemachine can feed -events to potentially millions of different state machine instances. - -This example uses a `Redis` instance to persist state machine -instances. - -Obviously, a million state machine instances in a JVM would be -a bad idea, due to memory constraints. This leads to -other features of Spring Statemachine that let you persist a -`StateMachineContext` and re-use existing instances. - -For this example, we assume that a shopping application -sends different types of `PageView` events to a separate -microservice which then tracks user behavior by using a state -machine. The following image shows the state model, which has a few states -that represent a user navigating a product items list, adding and removing -items from a cart, going to a payment page, and initiating a payment -operation: - -image::images/statechart14.png[width=500] - -An actual shopping application would send these events into -this service by (for example) using a rest call. More about this -later. - -NOTE: Remember that the focus here is to have an application that exposes a -`REST` API that the user can use to send events that can be processed by a -state machine for each request. - -The following state machine configuration models what we have in a -state chart. Various actions update the state machine's `Extended -State` to track the number of entries into various states and also how -many times the internal transitions for `ADD` and `DEL` are called and whether -`PAY` has been executed: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetC] ----- -==== - -Do not focus on `stateMachineTarget` or -`@Scope` for now, as we explain those later in this section. - -We set up a `RedisConnectionFactory` that defaults to -localhost and default port. We use `StateMachinePersist` with a -`RepositoryStateMachinePersist` implementation. Finally, we create a -`RedisStateMachinePersister` that uses a previously -created `StateMachinePersist` bean. - -These are then used in a `Controller` that handles `REST` calls, -as the following listing shows: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetD] ----- -==== - -We create a bean named `stateMachineTarget`. -State machine instantiation is a relatively -expensive operation, so it is better to try to pool instances instead -of instantiating a new instance for every request. To do so, we first -create a `poolTargetSource` that wraps `stateMachineTarget` and pools -it with a max size of three. When then proxy this `poolTargetSource` with -`ProxyFactoryBean` by using a `request` scope. Effectively, this means -that every `REST` request gets a pooled state machine instance from -a bean factory. Later, we show how these instances are used. -The following listing shows how we create the `ProxyFactoryBean` -and set the target source: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetA] ----- -==== - -The following listing shows we set the maximum size and set the target bean name: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetB] ----- -==== - -Now we can get into actual demo. You need to have a Redis server running on -localhost with default settings. Then you need to run the Boot-based sample -application by running the following command: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-eventservice-{revnumber}.jar ----- -==== - -In a browser, you see something like the following: - -image::images/sm-eventservice-1.png[width=500] - -In this UI, you can use three users: `joe`, `bob`, and `dave`. -Clicking a button shows the current state and the extended state. Enabling a -radio button before clicking a button sends a particular event for that -user. This arrangement lets you play with the UI. - -In our `StateMachineController`, we autowire `StateMachine` and -`StateMachinePersister`. `StateMachine` is `request` scoped, so you -get a new instance for each request, while `StateMachinePersist` is a normal -singleton bean. -The following listing autowires `StateMachine` and -`StateMachinePersist`: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineController.java[tags=snippetA] ----- -==== - -In the following listing, `feedAndGetState` is used with a UI to do same things that an -actual `REST` api might do: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineController.java[tags=snippetB] ----- -==== - -In the following listing, `feedPageview` is a `REST` method that accepts a post with -JSON content. - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineController.java[tags=snippetC] ----- -==== - -In the following listing, `feedMachine` sends an event into a `StateMachine` and persists -its state by using a `StateMachinePersister`: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineController.java[tags=snippetD] ----- -==== - -The following listing shows a `resetStateMachineFromStore` that is used to restore a state machine -for a particular user: - -==== -[source,java,indent=0] ----- -include::samples/demo/eventservice/StateMachineController.java[tags=snippetE] ----- -==== - -As you would usually send an event by using a UI, you can do the same by using `REST` calls, -as the following curl command shows: - -==== -[source,text] ----- -# curl http://localhost:8080/feed -H "Content-Type: application/json" --data '{"user":"joe","id":"VIEW_I"}' ----- -==== - -At this point, you should have content in Redis with a key of -`testprefix:joe`, as the following example shows: - -==== -[source,text] ----- -$ ./redis-cli -127.0.0.1:6379> KEYS * -1) "testprefix:joe" ----- -==== - -The next three images show when state for `joe` has been changed from -`HOME` to `ITEMS` and when the `ADD` action has been executed. - -The following image the `ADD` event being sent: - -image::images/sm-eventservice-2.png[width=500] - -Now your are still on the `ITEMS` state, and the internal transition caused -the `COUNT` extended state variable to increase to `1`, as the following image shows: - -image::images/sm-eventservice-3.png[width=500] - -Now you can run the following `curl` rest call a few times (or do it through the UI) and -see the `COUNT` variable increase with every call: - -==== -[source,text] ----- -# curl http://localhost:8080/feed -H "Content-Type: application/json" # --data '{"user":"joe","id":"ADD"}' ----- -==== - -The following image shows the result of these operations: - -image::images/sm-eventservice-4.png[width=500] - -[[statemachine-examples-deploy]] -== Deploy - -The deploy example shows how you can use state machine concepts with -UML modeling to provide a generic error handling state. This state -machine is a relatively complex example of how you can use various features -to provide a centralized error handling concept. -The following image shows the deploy state machine: - -image::images/model-deployer.png[width=500] - -NOTE: The preceding state chart was designed by using the Eclipse Papyrus Plugin -(see<>) and imported into Spring StateMachine through the resulting UML -model file. Actions and guards defined in a model are resolved -from a Spring Application Context. - -In this state machine scenario, we have two different behaviors -(`DEPLOY` and `UNDEPLOY`) that user tries to execute. - -In the preceding state chart: - -* In the `DEPLOY` state, the `INSTALL` and `START` states are entered - conditionally. We enter `START` directly if a product is already - installed and have no need to try to `START` if install fails. -* In the `UNDEPLOY` state, we enter `STOP` conditionally if the application is - already running. -* Conditional choices for `DEPLOY` and `UNDEPLOY` are done through a - choice pseudostate within those states, and the choices are selected - by guards. -* We use exit point pseudostates to have a more controlled exit from the - `DEPLOY` and `UNDEPLOY` states. -* After exiting from `DEPLOY` and `UNDEPLOY`, we go through a junction - pseudostate to choose whether to go through an `ERROR` state - (if an error was added into an extended state). -* Finally, we go back to the `READY` state to process new requests. - -Now we can get to the actual demo. Run the boot based sample application -by running the following command: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-deploy-{revnumber}.jar ----- -==== - -In a browser, you can see something like the following image: - -image::images/sm-deploy-1.png[width=500] - -IMPORTANT: As we do not have real install, start, or stop functionality, we -simulate failures by checking the existence of particular message headers. - -Now you can start to send events to a machine and choose various -message headers to drive functionality. - -[[statemachine-examples-ordershipping]] -== Order Shipping - -The order shipping example shows how you can use state machine concepts -to build a simple order processing system. - -The following image shows a state chart that drives this order shipping sample. - -image::images/sm-ordershipping-1.png[scaledwidth="100%"] - -In the preceding state chart: - -* The state machine enters the `WAIT_NEW_ORDER` (default) state. -* The event `PLACE_ORDER` transitions into the `RECEIVE_ORDER` state and the entry - action (`entryReceiveOrder`) is executed. -* If the order is `OK`, the state machine goes into two regions, one handling order - production and one handling user-level payment. Otherwise, the state machine goes - into `CUSTOMER_ERROR`, which is a final state. -* The state machine loops in a lower region to remind the user to pay - until `RECEIVE_PAYMENT` is sent successfully to indicate correct - payment. -* Both regions go into waiting states (`WAIT_PRODUCT` and - `WAIT_ORDER`), where they are joined before the parent orthogonal state - (`HANDLE_ORDER`) is exited. -* Finally, the state machine goes through `SHIP_ORDER` to its final state - (`ORDER_SHIPPED`). - -The following command runs the sample: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-ordershipping-{revnumber}.jar ----- -==== - -In a browser, you can see something similar to the following image. You can start by choosing -a customer and an order to create a state machine. - -image::images/sm-ordershipping-2.png[scaledwidth="100%"] - -The state machine for a particular order is now created and you can start to play -with placing an order and sending a payment. Other settings (such as -`makeProdPlan`, `produce`, and `payment`) let you control how the state -machine works. -The following image shows the state machine waiting for an order: - -image::images/sm-ordershipping-3.png[scaledwidth="100%"] - -Finally, you can see what machine does by refreshing a page, as the following image shows: - -image::images/sm-ordershipping-4.png[scaledwidth="100%"] - -[[statemachine-examples-datajpa]] -== JPA Configuration - -The JPA configuration example shows how you can use state machine concepts -with a machine configuration kept in a database. This sample uses -an embedded H2 database with an H2 Console (to ease playing with the -database). - -This sample uses `spring-statemachine-autoconfigure` (which, by default, -auto-configures the repositories and entity classes needed for JPA). -Thus, you need only `@SpringBootApplication`. -The following example shows the `Application` class with the `@SpringBootApplication` annotation: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpa/Application.java[tags=snippetA] ----- -==== - -The following example shows how to create a `RepositoryStateMachineModelFactory`: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetB] ----- -==== - -You can use the following command to run the sample: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-datajpa-{revnumber}.jar ----- -==== - -Accessing the application at `http://localhost:8080` brings up a newly -constructed machine for each request. You can then choose to send -events to a machine. The possible events and machine configuration are -updated from a database with every request. -The following image shows the UI and the initial events that are created when -this state machine starts: - -image::images/sm-datajpa-1.png[scaledwidth="100%"] - -To access the embedded console, you can use the JDBC URL (which is `jdbc:h2:mem:testdb`, if it is -not already set). -The following image shows the H2 console: - -image::images/sm-datajpa-2.png[scaledwidth="100%"] - -From the console, you can see the database tables and modify -them as you wish. -The following image shows the result of a simple query in the UI: - -image::images/sm-datajpa-3.png[scaledwidth="100%"] - -Now that you have gotten this far, you have probably wondered how those default -states and transitions got populated into the database. Spring Data -has a nice trick to auto-populate repositories, and we -used this feature through `Jackson2RepositoryPopulatorFactoryBean`. -The following example shows how we create such a bean: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetA] ----- -==== - -The following listing shows the source of the data with which we populate the database: - -==== -[source,json,indent=0] ----- -include::samples/data.json[] ----- -==== - -[[statemachine-examples-datapersist]] -== Data Persist - -The data persist sample shows how you can state machine concepts -with a persisting machine in an external repository. This sample uses -an embedded H2 database with an H2 Console (to ease playing with the -database). Optionally, you can also enable Redis or MongoDB. - -This sample uses `spring-statemachine-autoconfigure` (which, by default, -auto-configures the repositories and entity classes needed for JPA). -Thus, you need only `@SpringBootApplication`. -The following example shows the `Application` class with the `@SpringBootApplication` annotation: - -==== -[source,java,indent=0] ----- -include::samples/demo/datapersist/Application.java[tags=snippetA] ----- -==== - -The `StateMachineRuntimePersister` interface works on the runtime -level of a `StateMachine`. Its implementation, -`JpaPersistingStateMachineInterceptor`, is meant to be used with a -JPA. -The following listing creates a `StateMachineRuntimePersister` bean: - -==== -[source,java,indent=0] ----- -include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetA] ----- -==== - -The following example shows how you can use a very similar configuration -to create a bean for MongoDB: - -==== -[source,java,indent=0] ----- -include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetB] ----- -==== - -The following example shows how you can use a very similar configuration -to create a bean for Redis: - -==== -[source,java,indent=0] ----- -include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetC] ----- -==== - -You can configure `StateMachine` to use runtime persistence by using the -`withPersistence` configuration method. -The following listing shows how to do so: - -==== -[source,java,indent=0] ----- -include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetD] ----- -==== - -This sample also uses `DefaultStateMachineService`, which makes it -easier to work with multiple machines. -The following listing shows how to create an instance of `DefaultStateMachineService`: - -==== -[source,java,indent=0] ----- -include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetE] ----- -==== - -The following listing shows the logic that drives the `StateMachineService` in this sample: - -==== -[source,java,indent=0] ----- -include::samples/demo/datapersist/StateMachineController.java[tags=snippetA] ----- -==== - -You can use the following command to run the sample: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar ----- -==== - -[NOTE] -==== -By default, the `jpa` profile is enabled in `application.yml`. If you want to try -other backends, enable either the `mongo` profile or the `redis` profile. -The following commands specify which profile to use (`jpa` is the default, -but we included it for the sake of completeness): - -===== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=jpa -# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=mongo -# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=redis ----- -===== -==== - -Accessing the application at http://localhost:8080 brings up a newly -constructed state machine for each request, and you can choose to send -events to a machine. The possible events and machine configuration are -updated from a database with every request. - -The state machines in this sample have a simple configuration with states 'S1' -to 'S6' and events 'E1' to 'E6' to transition the state machine between those -states. You can use two state machine identifiers (`datajpapersist1` and -`datajpapersist2`) to request a particular state machine. -The following image shows the UI that lets you pick a machine and an event and that shows -what happens when you do: - -image::images/sm-datajpapersist-1.png[scaledwidth="100%"] - -The sample defaults to using machine 'datajpapersist1' and goes to its -initial state 'S1'. -The following image shows the result of using those defaults: - -image::images/sm-datajpapersist-2.png[scaledwidth="100%"] - -If you send events `E1` and `E2` to the `datajpapersist1` state machine, its -state is persisted as 'S3'. -The following image shows the result of doing so: - -image::images/sm-datajpapersist-3.png[scaledwidth="100%"] - -If you then request state machine `datajpapersist1` but send no events, -the state machine is restored back to its persisted state, `S3`. - -[[statemachine-examples-datajpamultipersist]] -== Data Multi Persist - -The data multi ersist sample is an extension of two other samples: -<> and <>. -We still keep machine configuration in a database and persist into a -database. However, this time, we also have a machine that contains two orthogonal -regions, to show how those are persisted independently. This sample -also uses an embedded H2 database with an H2 Console (to ease playing -with the database). - -This sample uses `spring-statemachine-autoconfigure` (which, by default, -auto-configures the repositories and entity classes needed for JPA). -Thus, you need only `@SpringBootApplication`. -The following example shows the `Application` class with the `@SpringBootApplication` annotation: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpamultipersist/Application.java[tags=snippetA] ----- -==== - -As in the other data-driven samples, we again create a `StateMachineRuntimePersister`, -as the following listing shows: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetA] ----- -==== - -A `StateMachineService` bean makes it easier to work with a machines. -The following listing shows how to create such a bean: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetB] ----- -==== - -We use JSON data to import the configuration. -The following example creates a bean to do so: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetC] ----- -==== - -The following listing shows how we get a `RepositoryStateMachineModelFactory`: - -==== -[source,java,indent=0] ----- -include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetD] ----- -==== - -You can run the sample by using the following command: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-datajpamultipersist-{revnumber}.jar ----- -==== - -Accessing the application at `http://localhost:8080` brings up a newly -constructed machine for each request and lets you send -events to a machine. The possible events and the state machine configuration are -updated from a database for each request. We also print out -all state machine contexts and the current root machine, -as the following image shows: - -image::images/sm-datajpamultipersist-1.png[scaledwidth="100%"] - -The state machine named `datajpamultipersist1` is a simple "`flat`" machine where states `S1`, -`S2` and `S3` are transitioned by events `E1`, `E2`, and `E3` (respectively). -However, the state machine named `datajpamultipersist2` contains two -regions (`R1` and `R2`) directly under the root level. That is why this -root level machine really does not have a state. We need -that root level machine to host those regions. - -Regions `R1` and `R2` in the `datajpamultipersist2` state machine contains states -`S10`, `S11`, and `S12` and `S20`, `S21`, and `S22` (respectively). Events -`E10`, `E11`, and `E12` are used for region `R1` and events `E20`, `E21`, -and event `E22` is used for region `R2`. The following images shows what happens when we -send events `E10` and `E20` to the -`datajpamultipersist2` state machine: - -image::images/sm-datajpamultipersist-2.png[scaledwidth="100%"] - -Regions have their own contexts with their own IDs, and the actual -ID is postfixed with `#` and the region ID. As the following image shows, -different regions in a database have different contexts: - -image::images/sm-datajpamultipersist-3.png[scaledwidth="100%"] - -[[statemachine-examples-monitoring]] -== Monitoring - -The monitoring sample shows how you can use state machine concepts to -monitor state machine transitions and actions. -The following listing configures the state machine that we use for this sample: - -==== -[source,java,indent=0] ----- -include::samples/demo/monitoring/StateMachineConfig.java[tags=snippetA] ----- -==== - -You can use the following command to run the sample: - -==== -[source,text,subs="attributes"] ----- -# java -jar spring-statemachine-samples-monitoring-{revnumber}.jar ----- -==== - -The following image shows the state machine's initial state: - -image::images/sm-monitoring-1.png[scaledwidth="100%"] - -The following image shows the state of the state machine after we have -performed some actions: - -image::images/sm-monitoring-2.png[scaledwidth="100%"] - -You can view metrics from Spring Boot by running the following two `curl` -commands (shown with their output): - -==== -[source,json] ----- -# curl http://localhost:8080/actuator/metrics/ssm.transition.duration - -{ - "name":"ssm.transition.duration", - "measurements":[ - { - "statistic":"COUNT", - "value":3.0 - }, - { - "statistic":"TOTAL_TIME", - "value":0.007 - }, - { - "statistic":"MAX", - "value":0.004 - } - ], - "availableTags":[ - { - "tag":"transitionName", - "values":[ - "INITIAL_S1", - "EXTERNAL_S1_S2" - ] - } - ] -} ----- - -[source,json] ----- -# curl http://localhost:8080/actuator/metrics/ssm.transition.transit - -{ - "name":"ssm.transition.transit", - "measurements":[ - { - "statistic":"COUNT", - "value":3.0 - } - ], - "availableTags":[ - { - "tag":"transitionName", - "values":[ - "EXTERNAL_S1_S2", - "INITIAL_S1" - ] - } - ] -} ----- -==== - -You can also view tracing from Spring Boot by running the following `curl` -command (shown with its output): - -==== -[source,json] ----- -# curl http://localhost:8080/actuator/statemachinetrace - -[ - { - "timestamp":"2018-02-11T06:44:12.723+0000", - "info":{ - "duration":2, - "machine":null, - "transition":"EXTERNAL_S1_S2" - } - }, - { - "timestamp":"2018-02-11T06:44:12.720+0000", - "info":{ - "duration":0, - "machine":null, - "action":"demo.monitoring.StateMachineConfig$Config$$Lambda$576/1499688007@22b47b2f" - } - }, - { - "timestamp":"2018-02-11T06:44:12.714+0000", - "info":{ - "duration":1, - "machine":null, - "transition":"INITIAL_S1" - } - }, - { - "timestamp":"2018-02-11T06:44:09.689+0000", - "info":{ - "duration":4, - "machine":null, - "transition":"INITIAL_S1" - } - } -] ----- -==== +include::sm-examples-turnstile.adoc[] +include::sm-examples-showcase.adoc[] +include::sm-examples-cdplayer.adoc[] +include::sm-examples-tasks.adoc[] +include::sm-examples-washer.adoc[] +include::sm-examples-persist.adoc[] +include::sm-examples-zookeeper.adoc[] +include::sm-examples-web.adoc[] +include::sm-examples-scope.adoc[] +include::sm-examples-security.adoc[] +include::sm-examples-eventservice.adoc[] +include::sm-examples-deploy.adoc[] +include::sm-examples-ordershipping.adoc[] +include::sm-examples-datajpa.adoc[] +include::sm-examples-datapersist.adoc[] +include::sm-examples-datajpamultipersist.adoc[] +include::sm-examples-datajpapersist.adoc[] +include::sm-examples-monitoring.adoc[]