diff --git a/build.gradle b/build.gradle index 3f74fc14..66c9b0c2 100644 --- a/build.gradle +++ b/build.gradle @@ -246,12 +246,15 @@ configure(rootProject) { task copyDocsSamples(type: Copy) { from 'spring-statemachine-core/src/test/java/org/springframework/statemachine/docs' + from 'spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/docs' from 'spring-statemachine-samples/src/main/java/' from 'spring-statemachine-samples/washer/src/main/java/' from 'spring-statemachine-samples/tasks/src/main/java/' from 'spring-statemachine-samples/turnstile/src/main/java/' from 'spring-statemachine-samples/showcase/src/main/java/' from 'spring-statemachine-samples/cdplayer/src/main/java/' + from 'spring-statemachine-samples/persist/src/main/java/' + from 'spring-statemachine-samples/zookeeper/src/main/java/' include '**/*.java' into 'docs/src/reference/asciidoc/samples' } diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc index 9209f45d..8dbd8965 100644 --- a/docs/src/reference/asciidoc/appendix.adoc +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -304,3 +304,11 @@ still working together in a sense. This is why orthogonal regions can combine together a multiple simultaneous states within a single state in a state machine. +[appendix] +[[appendices-zookeeper]] +== Distributed State Machine with Zookeeper +This appendix provides more detailed technical documentation about +using a Zookeeper with a Spring State Machine. + +tbd. + diff --git a/docs/src/reference/asciidoc/images/statechart10.png b/docs/src/reference/asciidoc/images/statechart10.png new file mode 100644 index 00000000..1629ccc3 Binary files /dev/null and b/docs/src/reference/asciidoc/images/statechart10.png differ diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc index 0ea6b108..3db822bb 100644 --- a/docs/src/reference/asciidoc/index.adoc +++ b/docs/src/reference/asciidoc/index.adoc @@ -18,6 +18,7 @@ include::introduction.adoc[] [[springandsm]] include::sm.adoc[] +include::recipes.adoc[] include::sm-examples.adoc[] include::faq.adoc[] include::appendix.adoc[] diff --git a/docs/src/reference/asciidoc/recipes.adoc b/docs/src/reference/asciidoc/recipes.adoc new file mode 100644 index 00000000..254b568a --- /dev/null +++ b/docs/src/reference/asciidoc/recipes.adoc @@ -0,0 +1,130 @@ +[[statemachine-recipes]] += Recipes +This chapter contains documentation for existing built-in state +machine recipes. + +What exactly is a recipe? As Spring Statemachine is always going to be +a foundational framework meaning that its core will not have that much +higher level functionality or dependencies outside of a Spring +Framework. Correct usage of a state machine may be a little difficult +time to time and there's always some common use cases how state +machine can be used. Recipe modules are meant to provide a higher +level solutions to these common use cases and also provide examples +beyond samples how framework can be used. + +[NOTE] +==== +Recipes are a great way to make external contributions this Spring +Statemachine project. If you're not ready to contribute to the +framework core itself, a custom and common recipe is a great way to +share functionality among other users. +==== + +[[statemachine-recipes-persist]] +== Persist +Persist recipe is a simple utility which allows to use a single state +machine instance to persist and update a state of an arbitrary item in +a repository. + +Recipe's main class is `PersistStateMachineHandler` which assumes user +to do three different things: + +- An instance of a `StateMachine` needs to be used + with a `PersistStateMachineHandler`. States and Events are required + to be type of Strings. +- `PersistStateChangeListener` need to be registered with handler + order to react to persist request. +- Method `handleEventWithState` is used to orchestrate state changes. + +There is a sample demonstrating usage of this recipe at +<>. + +[[statemachine-recipes-tasks]] +== Tasks +Tasks recipe is a concept to execute DAG of `Runnable` instances using +a state machine. This recipe has been developed from ideas introduced +in sample <>. + +Generic concept of a state machine is shown below. In this state chart +everything under `TASKS` just shows a generic concept of how a single +task is executed. Because this recipe allows to register deep +hierarcical DAG of tasks, meaning a real state chart would be deep +nested collection of sub-states and regions, there's no need to be +more presise. + +For example if you have only two registered tasks, below state chart +would be correct with `TASK_id` replaced with `TASK_1` and `TASK_2` if +registered tasks ids are `1` and `2`. + +image::images/statechart9.png[width=500] + +Executing a `Runnable` may result an error and especially if a complex +DAG of tasks is involved it is desirable that there is a way to handle +tasks execution errors and then having a way to continue execution +without executing already successfully executed tasks. Addition to +this it would be nice if some execution errors can be handled +automatically and as a last fallback, if error can't be handled +automatically, state maching is put into a state where user can handle +errors manually. + +`TasksHandler` contains a builder method to configure handler instance +and follows a simple builder patter. This builder can be used to +register `Runnable` tasks, `TasksListener` instances, define +`StateMachinePersist` hook, and setup custom `TaskExecutor` instance. + +Now lets take a simple `Runnable` just doing a simple sleep as shown +below. This is a base of all examples in this chapter. + +[source,java,indent=0] +---- +include::samples/DocsTasksSampleTests.java[tags=snippetAA] +---- + +To execute multiple `sleepRunnable` tasks just register tasks and +execute `runTasks()` method from `TasksHandler`. + +[source,java,indent=0] +---- +include::samples/DocsTasksSampleTests.java[tags=snippetB] +---- + +Order to listen what is happening with a task execution an instance of +a `TasksListener` can be registered with a `TasksHandler`. Recipe +provides an adapter `TasksListenerAdapter` if you dont' want to +implement a full interface. Listener provides a various hooks to +listen tasks execution events. + +[source,java,indent=0] +---- +include::samples/DocsTasksSampleTests.java[tags=snippetAB] +---- + +Listeners can be either registered via a builder or directly with a +`TasksHandler` as shown above. + +[source,java,indent=0] +---- +include::samples/DocsTasksSampleTests.java[tags=snippetC] +---- + +Above sample show how to create a deep nested DAG of tasks. Every task +needs to have an unique identifier and optionally as task can be +defined to be a sub-task. Effectively this will create a DAG of tasks. + +[source,java,indent=0] +---- +include::samples/DocsTasksSampleTests.java[tags=snippetD] +---- + +When error happens and a state machine running these tasks goes into a +`ERROR` state, user can call handler methods `fixCurrentProblems` to +reset current state of tasks kept in a state machine extended state +variables. Handler method `continueFromError` can then be used to +instruct state machine to transition from `ERROR` state back to +`READY` state where tasks can be executed again. + +[source,java,indent=0] +---- +include::samples/DocsTasksSampleTests.java[tags=snippetE] +---- + diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 930495fe..71f47c57 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -20,6 +20,7 @@ Every sample is located in its own directory under spring-shell and you will find usual boot fat jars under every sample projects `build/libs` directory. +[[statemachine-examples-turnstile]] == Turnstile Turnstile is a simple device which gives you an access if payment is @@ -442,6 +443,7 @@ What happened in above run: * We print lcd status and request next track. * We stop playing. +[[statemachine-examples-tasks]] == Tasks Tasks is a sample demonstrating a parallel task handling within a @@ -701,3 +703,194 @@ What happened in above run: * State is restored via HISTORY state which takes state machine back to its previous known state. +[[statemachine-examples-persist]] +== Persist +Persist is a sample using recipe <> to +demonstate how a database entry update logic can be controlled by a +state machine. + +The state machine logic and configuration is shown above: + +image::images/statechart10.png[width=500] + +.StateMachine Config +[source,java,indent=0] +---- +include::samples/demo/persist/Application.java[tags=snippetA] +---- + +`PersistStateMachineHandler` can be created using a below config: + +.Handler Config +[source,java,indent=0] +---- +include::samples/demo/persist/Application.java[tags=snippetB] +---- + +Order class used with this sample is shown below: + +.Order Class +[source,java,indent=0] +---- +include::samples/demo/persist/Application.java[tags=snippetC] +---- + +Now let's see how this example works. + +[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] +---- + +What happened in above run: + +* We listed rows from an existing embedded database which is already + populated with sample data. +* We request to update order `1` into `PROCESSING` state. +* We list db entries again and see that state has been changed from + `PLACED` into a `PROCESSING`. +* We do update for order `3` to update state from `SENT` into + `DELIVERED`. + +[NOTE] +==== +If you're wondering where is the database because there are literally no +signs of it in a sample code. Sample is based on Spring Boot and +because necessary classes are in a classpath, embedded `HSQL` instance +is created automatically. + +Spring Boot will even create an instance of `JdbcTemplate` which you +can just autowire like how it's done in `Persist.java`. + +[source,java,indent=0] +---- +include::samples/demo/persist/Persist.java[tags=snippetA] +---- +==== + +Finally we need to handle state changes: + +[source,java,indent=0] +---- +include::samples/demo/persist/Persist.java[tags=snippetB] +---- + +And use a `PersistStateChangeListener` to update database: + +[source,java,indent=0] +---- +include::samples/demo/persist/Persist.java[tags=snippetC] +---- + +[[statemachine-examples-zookeeper]] +== Zookeeper +Zookeeper is a distributed version from sample +<>. + +[NOTE] +==== +This sample needs and external `Zookeeper` instance accessible from +`localhost` with default port and settings. +==== + +Configuration of this sample is almost same as `turnstile` sample. We +only add configuration for distributed state machine where we +configure `StateMachineEnsemble`. + +[source,java,indent=0] +---- +include::samples/demo/zookeeper/Application.java[tags=snippetA] +---- + +Actual `StateMachineEnsemble` needs to be created as bean together +with `CuratorFramework` client. + +[source,java,indent=0] +---- +include::samples/demo/zookeeper/Application.java[tags=snippetB] +---- + +Lets go through a simple example where two different shell instances are +started with command `java -jar +spring-statemachine-samples-zookeeper-1.0.0.BUILD-SNAPSHOT.jar`. + +First open first shell instance(do not start second instance yet). +When state machine is started it will end up into its initial state +`LOCKED`. Then send event `COIN` to transit into `UNLOCKED` state. + +.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 +---- + +Open second shell instance and start a state machine. You should see +that distributed state `UNLOCKED` is entered instead of default +initial state `LOCKED`. + +.Shell2 +[source,text] +---- +sm>sm start +State machine started + +sm>sm state +UNLOCKED +---- + +Then from either of a shells(we use second instance here) send event +`PUSH` to transit from `UNLOCKED` into `LOCKED` state. + +.Shell2 +[source,text] +---- +sm>sm event PUSH +Exit state UNLOCKED +Entry state LOCKED +Event PUSH send +---- + +In other shell you should see state getting changed automatically +based on distributed state kept in Zookeeper. + +.Shell1 +[source,text] +---- +sm>Exit state UNLOCKED +Entry state LOCKED +---- + diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 683fd8db..d54d2912 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -31,6 +31,7 @@ simply made code snippets less verbose by leaving other needed parts away. ==== +[[statemachine-config]] === Configuring States We'll get into more complex configuration examples a bit later but lets first start with a something simple. For most simple state @@ -208,6 +209,7 @@ states from a regions. include::samples/DocsConfigurationSampleTests.java[tags=snippetU] ---- +[[statemachine-config-commonsettings]] === Configuring Common Settings Some of a common state machine configuration can be set via a `ConfigurationConfigurer`. This allows to set `BeanFactory`, @@ -216,7 +218,7 @@ and register `StateMachineListener` instances. [source,java,indent=0] ---- -include::samples/DocsConfigurationSampleTests.java[tags=snippetY] +include::samples/DocsConfigurationSampleTests.java[tags=snippetYA] ---- State machine `autoStartup` flag is disabled by default because all @@ -236,6 +238,18 @@ start/stop events. Naturally it is not possible to listen a state machine start events if `autoStartup` is enabled unless listener can be registered during a configuration phase. +`DistributedStateMachine` is configured via `withDistributed()` which +allows to set a `StateMachineEnsemble` which if exists automatically +wraps created `StateMachine` with `DistributedStateMachine` and +enables distributed mode. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetYB] +---- + +More about distributed states, refer to section <>. + [[sm-factories]] == State Machine Factories There are use cases when state machine needs to be created dynamically @@ -523,3 +537,143 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetK] In your own bean you can then use this _@StatesOnTransition_ as is and use type safe `source` and `target`. +[[sm-accessor]] +== State Machine Accessor +`StateMachine` is a main interface to communicate with a state machine +itself. Time to time there is a need to get more dynamical and +programmatic access to internal structures of a state machine and its +nested machines and regions. For these use cases a `StateMachine` is +exposing a functional interface `StateMachineAccessor` which provides +an interface to get access to individual `StateMachine` and +`Region` instances. + +`StateMachineFunction` is a simple functional interface which allows +to apply `StateMachineAccess` interface into a state machine. With +jdk7 these will create a little verbose code but with jdk8 lambdas +things look relatively non-verbose. + +Method `doWithAllRegions` gives access to all `Region` instances in +a state machine. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetZA] +---- + +Method `doWithRegion` gives access to single `Region` instance in a +state machine. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetZB] +---- + +Method `withAllRegions` gives access to all `Region` instances in +a state machine. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetZC] +---- + +Method `withRegion` gives access to single `Region` instance in a +state machine. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetZD] +---- + +[[sm-interceptor]] +== State Machine Interceptor +Instead of using a `StateMachineListener` interface one option is to +use a `StateMachineInterceptor`. One conceptual difference is that an +interceptor can be used to intercept and stop a current state +change or transition logic. Instead of implementing full interface, +adapter class `StateMachineInterceptorAdapter` can be used to override +default no-op methods. + +[NOTE] +==== +There is one recipe <> and one sample +<> which are related to use of an +interceptor. +==== + +Interceptor can be registered via `StateMachineAccessor`. Concept of +an interceptor is relatively deep internal feature and thus is not +exposed directly via `StateMachine` interface. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetZH] +---- + + +[[sm-persist]] +== Persisting State Machine +Traditionally an instance of a state machine is used as is within a +running program. More dynamic behaviour is possible to achieve via +dynamic builders and factories which allows state machine +instantiation on-demand. Building an instance of a state machine is +relatively heavy operation so if there is a need to i.e. handle +arbitrary state change in a database using a state machine we need to +find a better and faster way to do it. + +Persist feature allows user to save a state of a state machine itself +into an external repository and later reset a state machine based of +serialized state. For example if you have a database table keeping +orders it would be way too expensive to update order state via a state +machine if a new instance would need to be build for every change. +Persist feature allows you to reset a state machine state without +instantiating a new state machine instance. + +[NOTE] +==== +There is one recipe <> and one sample +<> which provides more info about +persisting states. +==== + +While it is possible to build a custom persistence feature using a +`StateMachineListener` it has one conceptual problem. When listener +notifies a change of state, state change has already happened. If a +custom persistent method within a listener fails to update serialized +state in an external repository, state in a state machine and state in +an external repository are then in inconsistent state. + +State machine interceptor can be used instead of where attempt to save +serialized state into an external storage is done during the a state +change within a state machine. If this interceptor callback fails, +state change attempt will be halted and instead of ending into an +inconsistent state, user can then handle this error manually. Using +the interceptors are discussed in <>. + +[[sm-distributed]] +== Using Distributed States +Distributed state is probably one of a most compicated concepts of a +Spring State Machine. What exactly is a distributed state? A state +within a single state machine is naturally really simple to understand +but when there is a need to introduce a shared distributed state +thoughout a state machines, things will get a little complicated. + +For configuration support see section +<> and actual usage example see +sample <>. + +`Distributed State Machine` is implemented via a +`DistributedStateMachine` class which simply wraps an actual instance +of a `StateMachine`. `DistributedStateMachine` intercepts +communication with a `StateMachine` instance and works with +distributed state abstractions handled via interface +`StateMachineEnsemble`. Depending on an actual implementation +`StateMachinePersist` interface may also be used to serialize a +`StateMachineContext` which contains enought information to reset a +`StateMachine`. + +While `Distributed State Machine` is implemented via an abstraction, +only one implementation currently exists based on `Zookeeper`. + +Current technical documentation of a `Zookeeker` based distributed +state machine can be found from an appendice <>. + diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java index 8c1a77fc..eded8c56 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java @@ -38,6 +38,8 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.access.StateMachineAccess; +import org.springframework.statemachine.access.StateMachineFunction; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.action.SpelExpressionAction; import org.springframework.statemachine.annotation.OnTransition; @@ -46,17 +48,19 @@ import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.StateMachineBuilder; +import org.springframework.statemachine.config.StateMachineBuilder.Builder; import org.springframework.statemachine.config.StateMachineConfigurerAdapter; import org.springframework.statemachine.config.StateMachineFactory; -import org.springframework.statemachine.config.StateMachineBuilder.Builder; import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.configurers.StateConfigurer.History; +import org.springframework.statemachine.ensemble.StateMachineEnsemble; import org.springframework.statemachine.event.StateMachineEvent; import org.springframework.statemachine.guard.Guard; import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.state.State; +import org.springframework.statemachine.support.StateMachineInterceptor; import org.springframework.statemachine.transition.Transition; /** @@ -735,7 +739,7 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { } -// tag::snippetY[] +// tag::snippetYA[] @Configuration @EnableStateMachine public static class Config17 @@ -754,6 +758,118 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { } } -// end::snippetY[] +// end::snippetYA[] + +// tag::snippetYB[] + @Configuration + @EnableStateMachine + public static class Config18 + extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withDistributed() + .ensemble(stateMachineEnsemble()); + } + + @Bean + public StateMachineEnsemble stateMachineEnsemble() + throws Exception { + // naturally not null but should return ensemble instance + return null; + } + + } +// end::snippetYB[] + + public static class AccessorSamples { + + StateMachine stateMachine = null; + + void s1() { +// tag::snippetZA[] + stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess function) { + function.setRelay(stateMachine); + } + }); + + stateMachine.getStateMachineAccessor() + .doWithAllRegions(access -> access.setRelay(stateMachine)); +// end::snippetZA[] + } + + void s2() { +// tag::snippetZB[] + stateMachine.getStateMachineAccessor().doWithRegion(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess function) { + function.setRelay(stateMachine); + } + }); + + stateMachine.getStateMachineAccessor() + .doWithRegion(access -> access.setRelay(stateMachine)); +// end::snippetZB[] + } + + void s3() { +// tag::snippetZC[] + for (StateMachineAccess access : stateMachine.getStateMachineAccessor().withAllRegions()) { + access.setRelay(stateMachine); + } + + stateMachine.getStateMachineAccessor().withAllRegions() + .stream().forEach(access -> access.setRelay(stateMachine)); +// end::snippetZC[] + } + + void s4() { +// tag::snippetZD[] + stateMachine.getStateMachineAccessor() + .withRegion().setRelay(stateMachine); +// end::snippetZD[] + } + + } + + public static class InterceptorSamples { + + StateMachine stateMachine = null; + + void s1() { +// tag::snippetZH[] + stateMachine.getStateMachineAccessor() + .withRegion().addStateMachineInterceptor(new StateMachineInterceptor() { + + @Override + public StateContext preTransition(StateContext stateContext) { + return stateContext; + } + + @Override + public void preStateChange(State state, Message message, + Transition transition, StateMachine stateMachine) { + } + + @Override + public StateContext postTransition(StateContext stateContext) { + return stateContext; + } + + @Override + public void postStateChange(State state, Message message, + Transition transition, StateMachine stateMachine) { + } + }); +// end::snippetZH[] + } + + } } diff --git a/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java b/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java index 862b9d56..13f8ca7b 100644 --- a/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java +++ b/spring-statemachine-samples/persist/src/main/java/demo/persist/Application.java @@ -67,6 +67,7 @@ public class Application { } //end::snippetA[] +//tag::snippetB[] @Configuration static class PersistHandlerConfig { @@ -84,7 +85,9 @@ public class Application { } } +//end::snippetB[] +//tag::snippetC[] public static class Order { int id; String state; @@ -100,6 +103,7 @@ public class Application { } } +//end::snippetC[] public static void main(String[] args) throws Exception { Bootstrap.main(args); diff --git a/spring-statemachine-samples/persist/src/main/java/demo/persist/Persist.java b/spring-statemachine-samples/persist/src/main/java/demo/persist/Persist.java index dca88c3f..21554cfd 100644 --- a/spring-statemachine-samples/persist/src/main/java/demo/persist/Persist.java +++ b/spring-statemachine-samples/persist/src/main/java/demo/persist/Persist.java @@ -36,8 +36,10 @@ public class Persist { private final PersistStateMachineHandler handler; +//tag::snippetA[] @Autowired private JdbcTemplate jdbcTemplate; +//end::snippetA[] private final PersistStateChangeListener listener = new LocalPersistStateChangeListener(); @@ -62,6 +64,7 @@ public class Persist { return buf.toString(); } +//tag::snippetB[] public void change(int order, String event) { Order o = jdbcTemplate.queryForObject("select id, state from orders where id = ?", new Object[]{order}, new RowMapper() { public Order mapRow(ResultSet rs, int rowNum) throws SQLException { @@ -70,7 +73,9 @@ public class Persist { }); handler.handleEventWithState(MessageBuilder.withPayload(event).setHeader("order", order).build(), o.state); } +//end::snippetB[] +//tag::snippetC[] private class LocalPersistStateChangeListener implements PersistStateChangeListener { @Override @@ -82,5 +87,6 @@ public class Persist { } } } +//end::snippetC[] } diff --git a/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java b/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java index a6a2ee12..39cbe3c0 100644 --- a/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java +++ b/spring-statemachine-samples/zookeeper/src/main/java/demo/zookeeper/Application.java @@ -32,18 +32,19 @@ import org.springframework.statemachine.zookeeper.ZookeeperStateMachineEnsemble; @Configuration public class Application { -//tag::snippetA[] @Configuration @EnableStateMachine static class StateMachineConfig extends StateMachineConfigurerAdapter { +//tag::snippetA[] @Override public void configure(StateMachineConfigurationConfigurer config) throws Exception { config .withDistributed() .ensemble(stateMachineEnsemble()); } +//end::snippetA[] @Override public void configure(StateMachineStateConfigurer states) @@ -69,6 +70,7 @@ public class Application { .event("PUSH"); } +//tag::snippetB[] @Bean public StateMachineEnsemble stateMachineEnsemble() throws Exception { return new ZookeeperStateMachineEnsemble(curatorClient(), "/foo"); @@ -79,27 +81,20 @@ public class Application { CuratorFramework client = CuratorFrameworkFactory.builder().defaultData(new byte[0]) .retryPolicy(new ExponentialBackoffRetry(1000, 3)) .connectString("localhost:2181").build(); - // for testing we start it here, thought initiator - // is trying to start it if not already done client.start(); return client; } - +//end::snippetB[] } -//end::snippetA[] -//tag::snippetB[] public static enum States { LOCKED, UNLOCKED } -//end::snippetB[] -//tag::snippetC[] public static enum Events { COIN, PUSH } -//end::snippetC[] public static void main(String[] args) throws Exception { Bootstrap.main(args);