diff --git a/build.gradle b/build.gradle index 4966f4de..97913530 100644 --- a/build.gradle +++ b/build.gradle @@ -451,6 +451,7 @@ configure(rootProject) { from 'spring-statemachine-samples/eventservice/src/main/java/' from 'spring-statemachine-samples/datajpa/src/main/java/' from 'spring-statemachine-samples/datajpa/src/main/resources/' + from 'spring-statemachine-samples/datajpapersist/src/main/java/' from 'spring-statemachine-samples/monitoring/src/main/java/' include '**/*.java' include '**/*.uml' diff --git a/docs/src/reference/asciidoc/images/sm-datajpa-3.png b/docs/src/reference/asciidoc/images/sm-datajpa-3.png index bbc1fd95..7ff394c9 100644 Binary files a/docs/src/reference/asciidoc/images/sm-datajpa-3.png and b/docs/src/reference/asciidoc/images/sm-datajpa-3.png differ diff --git a/docs/src/reference/asciidoc/images/sm-datajpapersist-1.png b/docs/src/reference/asciidoc/images/sm-datajpapersist-1.png new file mode 100644 index 00000000..9c324b73 Binary files /dev/null and b/docs/src/reference/asciidoc/images/sm-datajpapersist-1.png differ diff --git a/docs/src/reference/asciidoc/images/sm-datajpapersist-2.png b/docs/src/reference/asciidoc/images/sm-datajpapersist-2.png new file mode 100644 index 00000000..209fd7b9 Binary files /dev/null and b/docs/src/reference/asciidoc/images/sm-datajpapersist-2.png differ diff --git a/docs/src/reference/asciidoc/images/sm-datajpapersist-3.png b/docs/src/reference/asciidoc/images/sm-datajpapersist-3.png new file mode 100644 index 00000000..544b4639 Binary files /dev/null and b/docs/src/reference/asciidoc/images/sm-datajpapersist-3.png differ diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 97f73609..4512f35a 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -38,6 +38,8 @@ normal build cycle. Samples in this chapter are: <> JPA Config. +<> JPA Persist. + <> Monitoring. @@ -1495,6 +1497,87 @@ Actual source for populator data is shown below. include::samples/data.json[] ---- +[[statemachine-examples-datajpapersist]] +== JPA Persist +JPA Persist is an example how state machine concepts can be used +with persisting machine in a database. This sample is using +embedded _H2_ database with a _H2 Console_ to ease playing with a +database. + +This sample uses `spring-statemachine-boot` which on default +auto-configures repositories and entity classes needed for JPA. +Thus only `@SpringBootApplication` is needed. + +[source,java,indent=0] +---- +include::samples/demo/datajpapersist/Application.java[tags=snippetA] +---- + +`StateMachineRuntimePersister` is a new interface working on a runtime +level of a 'StateMachine' and its implementation +`JpaPersistingStateMachineInterceptor` is meant to be used with a +'JPA'. + +[source,java,indent=0] +---- +include::samples/demo/datajpapersist/StateMachineConfig.java[tags=snippetA] +---- + +'StateMachine' can be configured to use runtime persistence by using +`withPersistence` config method. + +[source,java,indent=0] +---- +include::samples/demo/datajpapersist/StateMachineConfig.java[tags=snippetB] +---- + +In this sample we also use `DefaultStateMachineService` which makes it +easier to work with multiple machines + +[source,java,indent=0] +---- +include::samples/demo/datajpapersist/StateMachineConfig.java[tags=snippetC] +---- + +A logic using a `StateMachineService` in this sample is show below. + +[source,java,indent=0] +---- +include::samples/demo/datajpapersist/StateMachineController.java[tags=snippetA] +---- + +Let's get into actual demo. Run the boot based sample application: + +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datajpapersist-{revnumber}.jar +---- + +Accessing application via _http://localhost:8080_ brings up a new +constructed machine with every request and you can choose to send +events to a machine. Possible events and machine configuration are +updated from a database with every request. + +Machines in this sample have a simple configuration with states 'S1' +to 'S6' and events 'E1' to 'E6' transitioning machine between those +states. Two machine identifiers 'datajpapersist1' and +'datajpapersist2' can be used to request particular machine. + +image::images/sm-datajpapersist-1.png[scaledwidth="100%"] + +Sample defaults to using machine 'datajpapersist1' and goes to its +initial state 'S1'. + +image::images/sm-datajpapersist-2.png[scaledwidth="100%"] + +If events 'E1' and 'E2' are sent into machine 'datajpapersist1' its +state is persisted as 'S3'. + +image::images/sm-datajpapersist-3.png[scaledwidth="100%"] + +If requesting machine 'datajpapersist1' by not sending any events, +machine is restored back to its persisted state 'S3'. + [[statemachine-examples-monitoring]] == Monitoring Monitoring is an example how state machine concepts can be used to diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 2b9353ae..348b56dc 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -36,6 +36,8 @@ that Spring Statemachine provides to any Spring based application. <> the state machine interceptor support. +<> the state machine service support. + <> the state machine persisting support. <> the Spring Boot support. @@ -490,6 +492,13 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetYC] More about config model, refer to section <>. +[NOTE] +==== +Config methods `withSecurity`, `withMonitoring` and `withPersistence` +are documented in sections <>, <> and +<> respectively. +==== + === Configuring Model `StateMachineModelFactory` is a hook to configure statemachine model without using a manual configuration. Essentially it is a third party @@ -1758,6 +1767,19 @@ Actions defined for transitions also have their own error handling logic <>. ==== +[[sm-service]] +== State Machine Services +StateMachine services are higher level implementations meant to +provide more user level functionalities to ease normal runtime +operations. Currently only one service interface +<> exists. + +[[sm-service-statemachineservice]] +=== Using StateMachineService +`StateMachineService` is an interface meant to handle running machines +and have a simple methods to 'acquire' and 'release' machines. It has +one default implementation named `DefaultStateMachineService`. + [[sm-persist]] == Persisting State Machine Traditionally an instance of a state machine is used as is within a @@ -1869,6 +1891,24 @@ Check sample <> for detailed usage. `RedisConnectionFactory` for it to work and we recommend a `JedisConnectionFactory` for it as seeing from above example. +[[sm-persist-statemachineruntimepersister]] +=== Using StateMachineRuntimePersister +`StateMachineRuntimePersister` is a simple extension to +`StateMachinePersist` adding interface level method to get +`StateMachineInterceptor` associated with it. This interceptor is then +required to persist machine during state changes without needing to +stop and start a machine. + +Currently there are implementations for this interface for out-of-the-box +supported _Spring Data Repositories_. These are +`JpaStateMachineRuntimePersister`, `RedisStateMachineRuntimePersister` +and `MongoDbStateMachineRuntimePersister`. + +[TIP] +==== +Check sample <> for detailed usage. +==== + [[sm-boot]] == Spring Boot Support Auto-configuration module `spring-statemachine-boot` contains all @@ -2356,7 +2396,12 @@ sub-state. image::images/papyrus-gs-15.png[scaledwidth="100%"] [[sm-repository]] -== Repository Config Support +== Repository Support +This section contains documentation related to using 'Spring Data +Repositories' used in State Machine. + +[[sm-repository-config]] +=== Repository Config It is also possible to keep machine configuration in an external storage where it will be loaded on demand instead of creating a static configuration either using _JavaConfig_ or _UML_ based config. This @@ -2402,14 +2447,26 @@ image::images/sm-repository-simplesubmachine.png[scaledwidth="100%", title="Simp [[image-sm-repository-showcasemachine]] image::images/sm-repository-showcasemachine.png[scaledwidth="100%", title="ShowcaseMachine"] -[[sm-repository-jpa]] -=== JPA +[[sm-repository-config-jpa]] +==== JPA Actual _Repository_ implementations for a _JPA_ are `JpaStateRepository`, `JpaTransitionRepository`, `JpaActionRepository` and `JpaGuardRepository` which are backed by _Entity_ classes `JpaRepositoryState`, `JpaRepositoryTransition`, `JpaRepositoryAction` and `JpaRepositoryGuard` respectively. +[IMPORTANT] +==== +Version '1.2.8' unfortunately had to made a change into JPA's _Entity_ +model regarding used table names. Previously generated table names +always had a prefix 'JPA_REPOSITORY_' derived from _Entity_ class +names. As this caused breaking issues with databases imposing +restrictions on database object lengths, all _Entity_ classes have +spesific definitions to force table names. For example +'JPA_REPOSITORY_STATE' is now simple 'STATE' and so on with other +_Entity_ classes. +==== + Generic way to update states and transition manually for jpa is shown below. This is equivalent to machine shown in <>. @@ -2463,8 +2520,8 @@ Complete example can be found from sample repository can be pre-populated from existing _json_ file having a definitions for entity classes. -[[sm-repository-redis]] -=== Redis +[[sm-repository-config-redis]] +==== Redis Actual _Repository_ implementations for a _Redis_ are `RedisStateRepository`, `RedisTransitionRepository`, `RedisActionRepository` and `RedisGuardRepository` which are backed by @@ -2488,8 +2545,8 @@ This is equivalent to machine shown in include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetB] ---- -[[sm-repository-mongodb]] -=== MongoDB +[[sm-repository-config-mongodb]] +==== MongoDB Actual _Repository_ implementations for a _MongoDB_ are `MongoDbStateRepository`, `MongoDbTransitionRepository`, `MongoDbActionRepository` and `MongoDbGuardRepository` which are backed by @@ -2513,3 +2570,55 @@ This is equivalent to machine shown in include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetB] ---- +[[sm-repository-persistence]] +=== Repository Persistence +Apart from storing machine configuration, shown in +<>, in an external repository it is also +possible to persist machine into repositories. + +Interface `StateMachineRepository` is a central access point +interacting with machine persistence and is backed by _Entity_ class +`RepositoryStateMachine`. + +[[sm-repository-persistence-jpa]] +==== JPA +Actual _Repository_ implementation for a _JPA_ is +`JpaStateMachineRepository` which is backed by _Entity_ class +`JpaRepositoryStateMachine`. + +Generic way to persist machine for jpa is shown below. This is +equivalent to machine shown in <>. + +[source,java,indent=0] +---- +include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetD] +---- + +[[sm-repository-persistence-redis]] +==== Redis +Actual _Repository_ implementation for a _Redis_ is +`RedisStateMachineRepository` which is backed by _Entity_ class +`RedisRepositoryStateMachine`. + +Generic way to persist machine for jpa is shown below. This is +equivalent to machine shown in <>. + +[source,java,indent=0] +---- +include::samples/DocsRedisRepositorySampleTests1.java[tags=snippetC] +---- + +[[sm-repository-persistence-mongodb]] +==== MongoDB +Actual _Repository_ implementation for a _MongoDB_ is +`MongoDbStateMachineRepository` which is backed by _Entity_ class +`MongoDbRepositoryStateMachine`. + +Generic way to persist machine for jpa is shown below. This is +equivalent to machine shown in <>. + +[source,java,indent=0] +---- +include::samples/DocsMongoDbRepositorySampleTests1.java[tags=snippetC] +---- + diff --git a/docs/src/reference/asciidoc/whatsnew.adoc b/docs/src/reference/asciidoc/whatsnew.adoc index 69d1e3b7..504e4f75 100644 --- a/docs/src/reference/asciidoc/whatsnew.adoc +++ b/docs/src/reference/asciidoc/whatsnew.adoc @@ -35,3 +35,10 @@ _UML_ support and integrations with external config repositories. * Initial work for _Spring Boot_ support. <> * Support for tracing and monitoring. <> +=== In 1.2.8 +_Spring Statemachine 1.2.8_ contains a bit more functionality normally +not seen in a point release but these changes didn't merit a fork of +_Spring Statemachine 1.3_. + +* JPA entity classes have changed table names <> + diff --git a/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java b/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java index 4ec4f827..46417746 100644 --- a/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java +++ b/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/JpaRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 the original author or authors. + * Copyright 2016-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -246,6 +246,28 @@ public class JpaRepositoryTests extends AbstractRepositoryTests { assertThat(transitionsRepository.count(), is(2l)); } + @Test + public void testRepository7() { + context.register(TestConfig.class); + context.refresh(); + + JpaStateMachineRepository stateMachineRepository = context.getBean(JpaStateMachineRepository.class); + + JpaRepositoryStateMachine machine1 = new JpaRepositoryStateMachine(); + machine1.setMachineId("machine1"); + machine1.setState("S1"); + machine1.setStateMachineContext(new byte[] { 0 }); + + assertThat(stateMachineRepository.count(), is(0l)); + stateMachineRepository.save(machine1); + assertThat(stateMachineRepository.count(), is(1l)); + + JpaRepositoryStateMachine machine1x = stateMachineRepository.findOne("machine1"); + assertThat(machine1x.getMachineId(), is(machine1.getMachineId())); + assertThat(machine1x.getState(), is(machine1.getState())); + assertThat(machine1x.getStateMachineContext().length, is(1)); + } + @Override protected AnnotationConfigApplicationContext buildContext() { return new AnnotationConfigApplicationContext(); diff --git a/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs/DocsJpaRepositorySampleTests1.java b/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs/DocsJpaRepositorySampleTests1.java index 87141894..11055f48 100644 --- a/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs/DocsJpaRepositorySampleTests1.java +++ b/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs/DocsJpaRepositorySampleTests1.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,13 @@ import java.util.HashSet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.statemachine.data.ActionRepository; import org.springframework.statemachine.data.GuardRepository; +import org.springframework.statemachine.data.StateMachineRepository; import org.springframework.statemachine.data.StateRepository; import org.springframework.statemachine.data.TransitionRepository; import org.springframework.statemachine.data.jpa.JpaRepositoryAction; import org.springframework.statemachine.data.jpa.JpaRepositoryGuard; import org.springframework.statemachine.data.jpa.JpaRepositoryState; +import org.springframework.statemachine.data.jpa.JpaRepositoryStateMachine; import org.springframework.statemachine.data.jpa.JpaRepositoryTransition; import org.springframework.statemachine.transition.TransitionKind; @@ -206,4 +208,23 @@ public class DocsJpaRepositorySampleTests1 { // end::snippetC4[] } } + + public static class Config4 { + +// tag::snippetD[] + @Autowired + StateMachineRepository stateMachineRepository; + + void persist() { + + JpaRepositoryStateMachine machine = new JpaRepositoryStateMachine(); + machine.setMachineId("machine"); + machine.setState("S1"); + // raw byte[] representation of a context + machine.setStateMachineContext(new byte[] { 0 }); + + stateMachineRepository.save(machine); + } +// end::snippetD[] + } } diff --git a/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/docs/DocsMongoDbRepositorySampleTests1.java b/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/docs/DocsMongoDbRepositorySampleTests1.java index 905e045d..296b3bf3 100644 --- a/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/docs/DocsMongoDbRepositorySampleTests1.java +++ b/spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/docs/DocsMongoDbRepositorySampleTests1.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,11 @@ package org.springframework.statemachine.data.mongodb.docs; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.statemachine.data.StateMachineRepository; import org.springframework.statemachine.data.StateRepository; import org.springframework.statemachine.data.TransitionRepository; import org.springframework.statemachine.data.mongodb.MongoDbRepositoryState; +import org.springframework.statemachine.data.mongodb.MongoDbRepositoryStateMachine; import org.springframework.statemachine.data.mongodb.MongoDbRepositoryTransition; public class DocsMongoDbRepositorySampleTests1 { @@ -83,4 +85,24 @@ public class DocsMongoDbRepositorySampleTests1 { } // end::snippetB[] } + + public static class Config3 { + +// tag::snippetC[] + @Autowired + StateMachineRepository stateMachineRepository; + + void persist() { + + MongoDbRepositoryStateMachine machine = new MongoDbRepositoryStateMachine(); + machine.setMachineId("machine"); + machine.setState("S1"); + // raw byte[] representation of a context + machine.setStateMachineContext(new byte[] { 0 }); + + stateMachineRepository.save(machine); + } +// end::snippetC[] + } + } diff --git a/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/docs/DocsRedisRepositorySampleTests1.java b/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/docs/DocsRedisRepositorySampleTests1.java index 5b3b6f01..94166652 100644 --- a/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/docs/DocsRedisRepositorySampleTests1.java +++ b/spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/docs/DocsRedisRepositorySampleTests1.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,11 @@ package org.springframework.statemachine.data.redis.docs; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.statemachine.data.StateMachineRepository; import org.springframework.statemachine.data.StateRepository; import org.springframework.statemachine.data.TransitionRepository; import org.springframework.statemachine.data.redis.RedisRepositoryState; +import org.springframework.statemachine.data.redis.RedisRepositoryStateMachine; import org.springframework.statemachine.data.redis.RedisRepositoryTransition; public class DocsRedisRepositorySampleTests1 { @@ -76,4 +78,23 @@ public class DocsRedisRepositorySampleTests1 { } // end::snippetB[] } + + public static class Config3 { + +// tag::snippetC[] + @Autowired + StateMachineRepository stateMachineRepository; + + void persist() { + + RedisRepositoryStateMachine machine = new RedisRepositoryStateMachine(); + machine.setMachineId("machine"); + machine.setState("S1"); + // raw byte[] representation of a context + machine.setStateMachineContext(new byte[] { 0 }); + + stateMachineRepository.save(machine); + } +// end::snippetC[] + } } diff --git a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java index 65ac8c88..3d2a427f 100644 --- a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java +++ b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,11 +35,11 @@ import org.springframework.statemachine.service.StateMachineService; @Configuration public class StateMachineConfig { -//tag::snippetA[] @Configuration @EnableStateMachineFactory public static class MachineConfig extends StateMachineConfigurerAdapter { +//tag::snippetB[] @Autowired private JpaStateMachineRepository jpaStateMachineRepository; @@ -50,6 +50,7 @@ public class StateMachineConfig { .withPersistence() .runtimePersister(stateMachineRuntimePersister()); } +//end::snippetB[] @Override public void configure(StateMachineStateConfigurer states) @@ -89,24 +90,26 @@ public class StateMachineConfig { .event(Events.E6); } +//tag::snippetA[] @Bean public StateMachineRuntimePersister stateMachineRuntimePersister() { return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository); } - } //end::snippetA[] + } @Configuration public static class ServiceConfig { +//tag::snippetC[] @Bean public StateMachineService stateMachineService(StateMachineFactory stateMachineFactory, StateMachineRuntimePersister stateMachineRuntimePersister) { return new DefaultStateMachineService(stateMachineFactory, stateMachineRuntimePersister); } +//end::snippetC[] } -//tag::snippetB[] public enum States { S1, S2, S3, S4, S5, S6; } @@ -114,5 +117,4 @@ public class StateMachineConfig { public enum Events { E1, E2, E3, E4, E5, E6; } -//end::snippetB[] } diff --git a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java index d62f409a..344cb766 100644 --- a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java +++ b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,6 +73,7 @@ public class StateMachineController { return "states"; } +//tag::snippetA[] private synchronized StateMachine getStateMachine(String machineId) throws Exception { listener.resetMessages(); if (currentStateMachine == null) { @@ -88,6 +89,7 @@ public class StateMachineController { } return currentStateMachine; } +//end::snippetA[] private Events[] getEvents() { return EnumSet.allOf(Events.class).toArray(new Events[0]);