diff --git a/build.gradle b/build.gradle index d72a936d..5087e76b 100644 --- a/build.gradle +++ b/build.gradle @@ -375,6 +375,7 @@ configure(rootProject) { from 'spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/docs' from 'spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/docs' from 'spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/docs' + from 'spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs' from 'spring-statemachine-samples/src/main/java/' from 'spring-statemachine-samples/washer/src/main/java/' from 'spring-statemachine-samples/tasks/src/main/java/' @@ -385,8 +386,11 @@ configure(rootProject) { from 'spring-statemachine-samples/zookeeper/src/main/java/' from 'spring-statemachine-samples/security/src/main/java/' from 'spring-statemachine-samples/eventservice/src/main/java/' + from 'spring-statemachine-samples/datajpa/src/main/java/' + from 'spring-statemachine-samples/datajpa/src/main/resources/' include '**/*.java' include '**/*.uml' + include '**/*.json' into 'docs/src/reference/asciidoc/samples' } diff --git a/docs/src/reference/asciidoc/getting-started.adoc b/docs/src/reference/asciidoc/getting-started.adoc index 87467acd..7581a5a4 100644 --- a/docs/src/reference/asciidoc/getting-started.adoc +++ b/docs/src/reference/asciidoc/getting-started.adoc @@ -16,7 +16,8 @@ dependencies outside of Spring Framework within its core system. Other optional parts like <> has dependencies to a `Zookeeper`, while <> has dependencies to spring-shell and spring-boot which pulls other dependencies -beyond framework itself. +beyond framework itself. Also optional security and data access has +dependencies to _Spring Security_ and _Spring Data Modules_. == Modules The following modules are available for Spring Statemachine. @@ -37,6 +38,12 @@ framework. |spring-statemachine-redis |`Redis` related features for state machine. +|spring-statemachine-data-common +|Common support module for `Spring Data`. + +|spring-statemachine-data-jpa +|Support module for `Spring Data JPA`. + |spring-statemachine-zookeeper |`Zookeeper` integration for a distributed state machine. @@ -47,7 +54,7 @@ framework. |Support module for Spring Cloud Cluster. |spring-statemachine-uml -|Support module for UI uml modeling. +|Support module for UI uml modeling with Eclipse Papyrus. |=== == Using Gradle diff --git a/docs/src/reference/asciidoc/images/sm-datajpa-1.png b/docs/src/reference/asciidoc/images/sm-datajpa-1.png new file mode 100644 index 00000000..5f0936b2 Binary files /dev/null and b/docs/src/reference/asciidoc/images/sm-datajpa-1.png differ diff --git a/docs/src/reference/asciidoc/images/sm-datajpa-2.png b/docs/src/reference/asciidoc/images/sm-datajpa-2.png new file mode 100644 index 00000000..cd43e6e9 Binary files /dev/null and b/docs/src/reference/asciidoc/images/sm-datajpa-2.png differ diff --git a/docs/src/reference/asciidoc/images/sm-datajpa-3.png b/docs/src/reference/asciidoc/images/sm-datajpa-3.png new file mode 100644 index 00000000..11e1f308 Binary files /dev/null and b/docs/src/reference/asciidoc/images/sm-datajpa-3.png differ diff --git a/docs/src/reference/asciidoc/samples/data.json b/docs/src/reference/asciidoc/samples/data.json new file mode 100644 index 00000000..2cdfb1d6 --- /dev/null +++ b/docs/src/reference/asciidoc/samples/data.json @@ -0,0 +1,29 @@ +[ + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": true, + "state": "S1" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": false, + "state": "S2" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": false, + "state": "S3" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", + "source": "S1", + "target": "S2", + "event": "E1" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", + "source": "S2", + "target": "S3", + "event": "E2" + } +] diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 12dff95e..465968b0 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -36,6 +36,8 @@ normal build cycle. Samples in this chapter are: <> Order Shipping. +<> JPA Config. + [source,text] ---- @@ -1425,3 +1427,69 @@ Finally you can see what machine does by refressing a page. image::images/sm-ordershipping-4.png[width=1000] +[[statemachine-examples-datajpa]] +== JPA Config +JPA Config is an example how state machine concepts can be used +with a machine configuration kept in a database. This sample is using +embedded _H2_ database with a _H2 Console_ to ease playing with a +database. + +To enable automatic scan of needed _Entity_ classes and _JPA +Repositories_, a boot application can be annotated with a custom +locations as shown below. + +[source,java,indent=0] +---- +include::samples/demo/datajpa/Application.java[tags=snippetA] +---- + +What comes for a machine config `RepositoryStateMachineModelFactory` +can be used as shown below. + +[source,java,indent=0] +---- +include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetB] +---- + +Lets get into actual demo. Run the boot based sample application: + +[source,text,subs="attributes"] +---- +# java -jar spring-statemachine-samples-datajpa-{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. + +image::images/sm-datajpa-1.png[width=1000] + +To access embedded console use _JDBC URL_ `jdbc:h2:mem:testdb` if it's +not already set. + +image::images/sm-datajpa-2.png[width=1000] + +From console you can see how database tables look like and modify +those as you wish. + +image::images/sm-datajpa-3.png[width=1000] + +Now that you got this far you probably wondered how those default +states and transitions got populated into a database. Spring Data +already have a nice trick to auto populate repositories and we simply +use this feature via `Jackson2RepositoryPopulatorFactoryBean`. + + +[source,java,indent=0] +---- +include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetA] +---- + +Actual source for populator data is shown below. + +[source,json,indent=0] +---- +include::samples/data.json[] +---- + diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 6397066c..73b55387 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 uml modeling support. +<> the state machine repository config support. + [[sm-config]] == Statemachine Configuration One of the common tasks when using a Statemachine is to design its @@ -2015,3 +2017,57 @@ sub-state. image::images/papyrus-gs-15.png[width=400] +[[sm-repository]] +== Repository Config Support + +[IMPORTANT] +==== +Repository abstraction support is still work in progress and scheduled +to be complete in future milestones. +==== + +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 +integration works via _Spring Data Repository_ abstraction. + +We have created special `StateMachineModelFactory` implementation +called `RepositoryStateMachineModelFactory` which is able to use base +repository interfaces `StateRepository` and `TransitionRepository` +accompanied with base entity interfaces `RepositoryState` and +`RepositoryTransition` respectively. + +Due to way how _Entities_ and _Repositories_ work in a _Spring Data_, +from a user perspective read access can be fully abstracted as it is +done in `RepositoryStateMachineModelFactory` as there is no need to +know what is a real mapped _Entity_ class _Repository_ is working +with. Writing into a _Repository_ is always dependant of using a real +_Repository_ specific _Entity_ class. From machine configuration point +of view we don't need to know these, meaning we don't need to know +actual implementation whether that is _JPA_, _Mongo_ or anything else +what _Spring Data_ supports. Using a real _Repository_ related +_Entity_ class comes into play when you manually try to write new +states or transitions into a backed repository. + +Actual out of a box implementations are documented in below sections. + +[[sm-repository-jpa]] +=== JPA +Currently one repository implementation exists which uses _JPA_ to +access configured database. + +Actual _Repository_ implementations for a _JPA_ are +`JpaStateRepository` and `JpaTransitionRepository` which are backed by +_Entity_ classes `JpaRepositoryState` and `JpaRepositoryTransition` +respectively. + +Generic way to update states and transition manually is shown below. + +[source,java,indent=0] +---- +include::samples/DocsJpaRepositorySampleTests1.java[tags=snippetA] +---- + +Complete example can be found from sample +<>. + 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 new file mode 100644 index 00000000..bd4b27e8 --- /dev/null +++ b/spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs/DocsJpaRepositorySampleTests1.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.data.jpa.docs; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.statemachine.data.StateRepository; +import org.springframework.statemachine.data.TransitionRepository; +import org.springframework.statemachine.data.jpa.JpaRepositoryState; +import org.springframework.statemachine.data.jpa.JpaRepositoryTransition; + +public class DocsJpaRepositorySampleTests1 { + + public static class Config1 { +// tag::snippetA[] + @Autowired + StateRepository stateRepository; + + @Autowired + TransitionRepository transitionRepository; + + void addConfig() { + JpaRepositoryState state1 = new JpaRepositoryState("machine1", "S1", true); + stateRepository.save(state1); + JpaRepositoryState state2 = new JpaRepositoryState("machine2", "S2", false); + stateRepository.save(state2); + + JpaRepositoryTransition transition1 = new JpaRepositoryTransition("machine1", "S1", "S2", "E1"); + JpaRepositoryTransition transition2 = new JpaRepositoryTransition("machine2", "S3", "S4", "E2"); + transitionRepository.save(transition1); + transitionRepository.save(transition2); + } +// end::snippetA[] + } +} diff --git a/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/Application.java b/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/Application.java index de67a281..f083da9f 100644 --- a/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/Application.java +++ b/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/Application.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.orm.jpa.EntityScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +//tag::snippetA[] @SpringBootApplication @EntityScan(basePackages = {"org.springframework.statemachine.data.jpa"}) @EnableJpaRepositories(basePackages = {"org.springframework.statemachine.data.jpa"}) @@ -29,3 +30,4 @@ public class Application { SpringApplication.run(Application.class, args); } } +//end::snippetA[] diff --git a/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/StateMachineConfig.java b/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/StateMachineConfig.java index c3efbb96..0ecbfadd 100644 --- a/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/StateMachineConfig.java +++ b/spring-statemachine-samples/datajpa/src/main/java/demo/datajpa/StateMachineConfig.java @@ -34,13 +34,16 @@ import org.springframework.statemachine.data.TransitionRepository; @Configuration public class StateMachineConfig { +//tag::snippetA[] @Bean public Jackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { Jackson2RepositoryPopulatorFactoryBean factoryBean = new Jackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[]{new ClassPathResource("data.json")}); return factoryBean; } +//end::snippetA[] +//tag::snippetB[] @Configuration @EnableStateMachineFactory public static class Config extends StateMachineConfigurerAdapter { @@ -63,4 +66,5 @@ public class StateMachineConfig { return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository); } } +//end::snippetB[] }