diff --git a/build.gradle b/build.gradle index 212c4298..98916615 100644 --- a/build.gradle +++ b/build.gradle @@ -199,6 +199,8 @@ project('spring-statemachine-data-common') { compile project(":spring-statemachine-core") compile "org.springframework.data:spring-data-commons:$springDataCommonsVersion" optional "org.springframework.security:spring-security-core:$springSecurityVersion" + compile "com.fasterxml.jackson.core:jackson-core:$jackson2Version" + compile "com.fasterxml.jackson.core:jackson-databind:$jackson2Version" } } diff --git a/docs/src/reference/asciidoc/samples/data.json b/docs/src/reference/asciidoc/samples/data.json index b95e9d7f..b0aebabe 100644 --- a/docs/src/reference/asciidoc/samples/data.json +++ b/docs/src/reference/asciidoc/samples/data.json @@ -1,5 +1,6 @@ [ { + "@id": "1", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": true, "state": "S1", @@ -11,6 +12,7 @@ ] }, { + "@id": "2", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S2", @@ -22,6 +24,7 @@ ] }, { + "@id": "3", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S3", @@ -34,15 +37,15 @@ }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S1", - "target": "S2", + "source": "1", + "target": "2", "event": "E1", "kind": "EXTERNAL" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S2", - "target": "S3", + "source": "2", + "target": "3", "event": "E2", "actions": [ { diff --git a/gradle.properties b/gradle.properties index acc52dbe..3cc347de 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,5 @@ eclipseUml2CommonVersion=2.0.0-v20140602-0749 eclipseEmfXmiVersion=2.11.1-v20150805-0538 eclipseEmfEcoreVersion=2.11.1-v20150805-0538 eclipseEmfCommonVersion=2.11.0-v20150805-0538 - +jackson2Version=2.8.3 +eclipsePersistenceVersion=2.1.1 diff --git a/spring-statemachine-data/build.gradle b/spring-statemachine-data/build.gradle index 7614460b..0df257a8 100644 --- a/spring-statemachine-data/build.gradle +++ b/spring-statemachine-data/build.gradle @@ -5,10 +5,11 @@ project('spring-statemachine-data-jpa') { dependencies { compile project(":spring-statemachine-data-common") compile "org.springframework:spring-orm:$springVersion" - optional "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" testCompile project(":spring-statemachine-test") + optional "org.eclipse.persistence:javax.persistence:$eclipsePersistenceVersion" testCompile "org.hsqldb:hsqldb:$hsqlVersion" testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" + testRuntime "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" testRuntime "org.springframework.boot:spring-boot-starter-web:$springBootVersion" } } diff --git a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryState.java b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryState.java index 7960b7c9..d39fa65e 100644 --- a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryState.java +++ b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryState.java @@ -27,6 +27,9 @@ import javax.persistence.OneToMany; import org.springframework.statemachine.data.RepositoryState; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; + /** * JPA entity for states. * @@ -34,7 +37,8 @@ import org.springframework.statemachine.data.RepositoryState; * */ @Entity -public class JpaRepositoryState implements RepositoryState { +@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class) +public class JpaRepositoryState extends RepositoryState { @Id @GeneratedValue(strategy = GenerationType.AUTO) @@ -186,4 +190,11 @@ public class JpaRepositoryState implements RepositoryState { public void setExitActions(Set exitActions) { this.exitActions = exitActions; } + + @Override + public String toString() { + return "JpaRepositoryState [id=" + id + ", machineId=" + machineId + ", parentState=" + parentState + ", state=" + state + + ", initial=" + initial + ", stateActions=" + stateActions + ", entryActions=" + entryActions + ", exitActions=" + + exitActions + "]"; + } } diff --git a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryTransition.java b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryTransition.java index e86da510..f29f67d3 100644 --- a/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryTransition.java +++ b/spring-statemachine-data/jpa/src/main/java/org/springframework/statemachine/data/jpa/JpaRepositoryTransition.java @@ -29,6 +29,9 @@ import javax.persistence.OneToOne; import org.springframework.statemachine.data.RepositoryTransition; import org.springframework.statemachine.transition.TransitionKind; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; + /** * JPA entity for transitions. * @@ -36,15 +39,21 @@ import org.springframework.statemachine.transition.TransitionKind; * */ @Entity -public class JpaRepositoryTransition implements RepositoryTransition { +@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class) +public class JpaRepositoryTransition extends RepositoryTransition { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String machineId; - private String source; - private String target; + + @OneToOne(fetch = FetchType.EAGER) + private JpaRepositoryState source; + + @OneToOne(fetch = FetchType.EAGER) + private JpaRepositoryState target; + private String event; private TransitionKind kind; @@ -68,7 +77,7 @@ public class JpaRepositoryTransition implements RepositoryTransition { * @param target the target * @param event the event */ - public JpaRepositoryTransition(String source, String target, String event) { + public JpaRepositoryTransition(JpaRepositoryState source, JpaRepositoryState target, String event) { this(null, source, target, event); } @@ -80,7 +89,7 @@ public class JpaRepositoryTransition implements RepositoryTransition { * @param target the target * @param event the event */ - public JpaRepositoryTransition(String machineId, String source, String target, String event) { + public JpaRepositoryTransition(String machineId, JpaRepositoryState source, JpaRepositoryState target, String event) { this(machineId, source, target, event, null); } @@ -93,7 +102,7 @@ public class JpaRepositoryTransition implements RepositoryTransition { * @param event the event * @param actions the actions */ - public JpaRepositoryTransition(String machineId, String source, String target, String event, Set actions) { + public JpaRepositoryTransition(String machineId, JpaRepositoryState source, JpaRepositoryState target, String event, Set actions) { this.machineId = machineId; this.source = source; this.target = target; @@ -111,20 +120,20 @@ public class JpaRepositoryTransition implements RepositoryTransition { } @Override - public String getSource() { + public JpaRepositoryState getSource() { return source; } - public void setSource(String source) { + public void setSource(JpaRepositoryState source) { this.source = source; } @Override - public String getTarget() { + public JpaRepositoryState getTarget() { return target; } - public void setTarget(String target) { + public void setTarget(JpaRepositoryState target) { this.target = target; } @@ -163,4 +172,10 @@ public class JpaRepositoryTransition implements RepositoryTransition { public void setKind(TransitionKind kind) { this.kind = kind; } + + @Override + public String toString() { + return "JpaRepositoryTransition [id=" + id + ", machineId=" + machineId + ", source=" + source + ", target=" + target + ", event=" + + event + ", kind=" + kind + ", actions=" + actions + ", guard=" + guard + "]"; + } } 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 173c3e64..f3243d52 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 @@ -31,7 +31,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.data.repository.init.Jackson2RepositoryPopulatorFactoryBean; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.StateMachineConfigurerAdapter; @@ -43,6 +42,7 @@ import org.springframework.statemachine.data.RepositoryStateMachineModelFactory; import org.springframework.statemachine.data.RepositoryTransition; import org.springframework.statemachine.data.StateRepository; import org.springframework.statemachine.data.TransitionRepository; +import org.springframework.statemachine.data.support.StateMachineJackson2RepositoryPopulatorFactoryBean; import org.springframework.statemachine.test.StateMachineTestPlan; import org.springframework.statemachine.test.StateMachineTestPlanBuilder; import org.springframework.statemachine.transition.TransitionKind; @@ -60,18 +60,24 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { context.refresh(); JpaStateRepository statesRepository = context.getBean(JpaStateRepository.class); - JpaRepositoryState state = new JpaRepositoryState("S1"); - statesRepository.save(state); - Iterable findAll = statesRepository.findAll(); - assertThat(findAll.iterator().next().getState(), is("S1")); + JpaRepositoryState stateS1 = new JpaRepositoryState("S1"); + JpaRepositoryState stateS2 = new JpaRepositoryState("S2"); + assertThat(statesRepository.count(), is(0l)); + + statesRepository.save(stateS1); + statesRepository.save(stateS2); + assertThat(statesRepository.count(), is(2l)); JpaTransitionRepository transitionsRepository = context.getBean(JpaTransitionRepository.class); - JpaRepositoryTransition transition = new JpaRepositoryTransition("S1", "S2", "E1"); + JpaRepositoryTransition transition = new JpaRepositoryTransition(stateS1, stateS2, "E1"); transition.setKind(TransitionKind.EXTERNAL); transitionsRepository.save(transition); + + assertThat(statesRepository.count(), is(2l)); + JpaRepositoryTransition transition2 = transitionsRepository.findAll().iterator().next(); - assertThat(transition2.getSource(), is("S1")); - assertThat(transition2.getTarget(), is("S2")); + assertThat(transition2.getSource().getState(), is("S1")); + assertThat(transition2.getTarget().getState(), is("S2")); assertThat(transition2.getEvent(), is("E1")); assertThat(transition2.getKind(), is(TransitionKind.EXTERNAL)); @@ -85,8 +91,10 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { @SuppressWarnings("unchecked") StateRepository statesRepository1 = context.getBean(StateRepository.class); - JpaRepositoryState state = new JpaRepositoryState("S1"); - statesRepository1.save(state); + JpaRepositoryState state1 = new JpaRepositoryState("S1"); + statesRepository1.save(state1); + JpaRepositoryState state2 = new JpaRepositoryState("S2"); + statesRepository1.save(state2); @SuppressWarnings("unchecked") StateRepository statesRepository2 = context.getBean(StateRepository.class); Iterable findAll = statesRepository2.findAll(); @@ -94,11 +102,11 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { @SuppressWarnings("unchecked") TransitionRepository transitionsRepository = context.getBean(TransitionRepository.class); - RepositoryTransition transition = new JpaRepositoryTransition("S1", "S2", "E1"); + RepositoryTransition transition = new JpaRepositoryTransition(state1, state2, "E1"); transitionsRepository.save(transition); RepositoryTransition transition2 = transitionsRepository.findAll().iterator().next(); - assertThat(transition2.getSource(), is("S1")); - assertThat(transition2.getTarget(), is("S2")); + assertThat(transition2.getSource().getState(), is("S1")); + assertThat(transition2.getTarget().getState(), is("S2")); assertThat(transition2.getEvent(), is("E1")); context.close(); @@ -114,18 +122,22 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { statesRepository.save(state1); JpaRepositoryState state2 = new JpaRepositoryState("machine2", "S2", false); statesRepository.save(state2); + JpaRepositoryState state3 = new JpaRepositoryState("machine1", "S3", true); + statesRepository.save(state3); + JpaRepositoryState state4 = new JpaRepositoryState("machine2", "S4", false); + statesRepository.save(state4); List findByMachineId1 = statesRepository.findByMachineId("machine1"); List findByMachineId2 = statesRepository.findByMachineId("machine2"); - assertThat(findByMachineId1.size(), is(1)); - assertThat(findByMachineId2.size(), is(1)); + assertThat(findByMachineId1.size(), is(2)); + assertThat(findByMachineId2.size(), is(2)); assertThat(findByMachineId1.get(0).getMachineId(), is("machine1")); assertThat(findByMachineId2.get(0).getMachineId(), is("machine2")); JpaTransitionRepository transitionsRepository = context.getBean(JpaTransitionRepository.class); - JpaRepositoryTransition transition1 = new JpaRepositoryTransition("machine1", "S1", "S2", "E1"); - JpaRepositoryTransition transition2 = new JpaRepositoryTransition("machine2", "S3", "S4", "E2"); + JpaRepositoryTransition transition1 = new JpaRepositoryTransition("machine1", state1, state2, "E1"); + JpaRepositoryTransition transition2 = new JpaRepositoryTransition("machine2", state3, state4, "E2"); transitionsRepository.save(transition1); transitionsRepository.save(transition2); List findByMachineId3 = transitionsRepository.findByMachineId("machine1"); @@ -161,10 +173,16 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { context.register(Config.class); context.refresh(); + JpaStateRepository statesRepository = context.getBean(JpaStateRepository.class); + JpaRepositoryState stateS1 = new JpaRepositoryState("S1"); + JpaRepositoryState stateS2 = new JpaRepositoryState("S2"); + statesRepository.save(stateS1); + statesRepository.save(stateS2); + JpaActionRepository actionsRepository = context.getBean(JpaActionRepository.class); JpaTransitionRepository transitionsRepository = context.getBean(JpaTransitionRepository.class); - JpaRepositoryTransition transition = new JpaRepositoryTransition("S1", "S2", "E1"); + JpaRepositoryTransition transition = new JpaRepositoryTransition(stateS1, stateS2, "E1"); JpaRepositoryAction action1 = new JpaRepositoryAction(); action1.setName("action1"); @@ -174,8 +192,8 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { transitionsRepository.save(transition); JpaRepositoryTransition transition2 = transitionsRepository.findAll().iterator().next(); - assertThat(transition2.getSource(), is("S1")); - assertThat(transition2.getTarget(), is("S2")); + assertThat(transition2.getSource().getState(), is("S1")); + assertThat(transition2.getTarget().getState(), is("S2")); assertThat(transition2.getEvent(), is("E1")); assertThat(actionsRepository.count(), is(1l)); @@ -224,6 +242,16 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { plan.test(); } + @Test + public void testPopulate1() { + context.register(Config2.class); + context.refresh(); + JpaStateRepository stateRepository = context.getBean(JpaStateRepository.class); + JpaTransitionRepository transitionRepository = context.getBean(JpaTransitionRepository.class); + assertThat(stateRepository.count(), is(3l)); + assertThat(transitionRepository.count(), is(3l)); + } + @Test public void testAutowire() { context.register(Config.class, WireConfig.class); @@ -258,8 +286,8 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { static class Config2 { @Bean - public Jackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { - Jackson2RepositoryPopulatorFactoryBean factoryBean = new Jackson2RepositoryPopulatorFactoryBean(); + public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { + StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[]{new ClassPathResource("data2.json")}); return factoryBean; } @@ -269,8 +297,8 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { static class Config3 { @Bean - public Jackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { - Jackson2RepositoryPopulatorFactoryBean factoryBean = new Jackson2RepositoryPopulatorFactoryBean(); + public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { + StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[]{new ClassPathResource("data3.json")}); return factoryBean; } 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 bd4b27e8..059f77fa 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 @@ -33,12 +33,16 @@ public class DocsJpaRepositorySampleTests1 { void addConfig() { JpaRepositoryState state1 = new JpaRepositoryState("machine1", "S1", true); - stateRepository.save(state1); JpaRepositoryState state2 = new JpaRepositoryState("machine2", "S2", false); + JpaRepositoryState state3 = new JpaRepositoryState("machine1", "S3", true); + JpaRepositoryState state4 = new JpaRepositoryState("machine2", "S4", false); + stateRepository.save(state1); stateRepository.save(state2); + stateRepository.save(state3); + stateRepository.save(state4); - JpaRepositoryTransition transition1 = new JpaRepositoryTransition("machine1", "S1", "S2", "E1"); - JpaRepositoryTransition transition2 = new JpaRepositoryTransition("machine2", "S3", "S4", "E2"); + JpaRepositoryTransition transition1 = new JpaRepositoryTransition("machine1", state1, state2, "E1"); + JpaRepositoryTransition transition2 = new JpaRepositoryTransition("machine2", state3, state4, "E2"); transitionRepository.save(transition1); transitionRepository.save(transition2); } diff --git a/spring-statemachine-data/jpa/src/test/resources/data2.json b/spring-statemachine-data/jpa/src/test/resources/data2.json index 092ca721..24318a74 100644 --- a/spring-statemachine-data/jpa/src/test/resources/data2.json +++ b/spring-statemachine-data/jpa/src/test/resources/data2.json @@ -1,30 +1,33 @@ [ { + "@id": "1", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": true, "state": "S1" }, { + "@id": "2", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S2" }, { + "@id": "3", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S3" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S1", - "target": "S2", + "source": "1", + "target": "2", "event": "E1", "kind": "EXTERNAL" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S2", - "target": "S3", + "source": "2", + "target": "3", "event": "E2", "guard": { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryGuard", @@ -33,8 +36,8 @@ }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S2", - "target": "S3", + "source": "2", + "target": "3", "event": "E3", "kind": "LOCAL" } diff --git a/spring-statemachine-data/jpa/src/test/resources/data3.json b/spring-statemachine-data/jpa/src/test/resources/data3.json index 88e66ab9..cf988f89 100644 --- a/spring-statemachine-data/jpa/src/test/resources/data3.json +++ b/spring-statemachine-data/jpa/src/test/resources/data3.json @@ -1,21 +1,25 @@ [ { + "@id": "1", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": true, "state": "S1" }, { + "@id": "2", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S2" }, { + "@id": "3", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": true, "parentState": "S2", "state": "S20" }, { + "@id": "4", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "parentState": "S2", @@ -23,26 +27,26 @@ }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S1", - "target": "S2", + "source": "1", + "target": "2", "event": "E1" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S20", - "target": "S21", + "source": "3", + "target": "4", "event": "E2" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S2", - "target": "S1", + "source": "2", + "target": "1", "event": "E3" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S1", - "target": "S21", + "source": "1", + "target": "4", "event": "E4" } ] diff --git a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/BaseRepositoryEntity.java b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/BaseRepositoryEntity.java new file mode 100644 index 00000000..cfc1dd31 --- /dev/null +++ b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/BaseRepositoryEntity.java @@ -0,0 +1,28 @@ +/* + * 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; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Generic base class for all entity classes. + * + * @author Janne Valkealahti + * + */ +@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="_class") +public abstract class BaseRepositoryEntity { +} diff --git a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryState.java b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryState.java index 8570457c..0ecd29b1 100644 --- a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryState.java +++ b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryState.java @@ -18,59 +18,59 @@ package org.springframework.statemachine.data; import java.util.Set; /** - * Generic interface representing state entity. + * Generic base class representing state entity. * * @author Janne Valkealahti * */ -public interface RepositoryState { +public abstract class RepositoryState extends BaseRepositoryEntity { /** * Gets the parent state. * * @return the parent state */ - String getParentState(); + public abstract String getParentState(); /** * Gets the machine id. * * @return the machine id */ - String getMachineId(); + public abstract String getMachineId(); /** * Gets the state. * * @return the state */ - String getState(); + public abstract String getState(); /** * Checks if is initial. * * @return true, if is initial */ - boolean isInitial(); + public abstract boolean isInitial(); /** * Gets the state actions. * * @return the state actions */ - Set getStateActions(); + public abstract Set getStateActions(); /** * Gets the entry actions. * * @return the entry actions */ - Set getEntryActions(); + public abstract Set getEntryActions(); /** * Gets the exit actions. * * @return the exit actions */ - Set getExitActions(); + public abstract Set getExitActions(); } diff --git a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachineModelFactory.java b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachineModelFactory.java index 0501b676..fe9ca78b 100644 --- a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachineModelFactory.java +++ b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryStateMachineModelFactory.java @@ -175,8 +175,7 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode guard = new SpelExpressionGuard<>(parser.parseExpression(repositoryGuard.getSpel())); } } - - transitionData.add(new TransitionData<>(t.getSource(), t.getTarget(), t.getEvent(), actions, guard, kind != null ? kind : TransitionKind.EXTERNAL)); + transitionData.add(new TransitionData<>(t.getSource().getState(), t.getTarget().getState(), t.getEvent(), actions, guard, kind != null ? kind : TransitionKind.EXTERNAL)); } TransitionsData transitionsData = new TransitionsData<>(transitionData); diff --git a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryTransition.java b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryTransition.java index 39dc5498..2c0d60ae 100644 --- a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryTransition.java +++ b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/RepositoryTransition.java @@ -20,59 +20,59 @@ import java.util.Set; import org.springframework.statemachine.transition.TransitionKind; /** - * Generic interface representing transition entity. + * Generic base class representing transition entity. * * @author Janne Valkealahti * */ -public interface RepositoryTransition { +public abstract class RepositoryTransition extends BaseRepositoryEntity { /** * Gets the machine id. * * @return the machine id */ - String getMachineId(); + public abstract String getMachineId(); /** * Gets the source. * * @return the source */ - String getSource(); + public abstract RepositoryState getSource(); /** * Gets the target. * * @return the target */ - String getTarget(); + public abstract RepositoryState getTarget(); /** * Gets the event. * * @return the event */ - String getEvent(); + public abstract String getEvent(); /** * Gets the actions. * * @return the actions */ - Set getActions(); + public abstract Set getActions(); /** * Gets the guard. * * @return the guard */ - RepositoryGuard getGuard(); + public abstract RepositoryGuard getGuard(); /** * Gets the transition kind. * * @return the transition kind */ - TransitionKind getKind(); + public abstract TransitionKind getKind(); } diff --git a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/support/StateMachineJackson2RepositoryPopulatorFactoryBean.java b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/support/StateMachineJackson2RepositoryPopulatorFactoryBean.java new file mode 100644 index 00000000..dc6f4359 --- /dev/null +++ b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/support/StateMachineJackson2RepositoryPopulatorFactoryBean.java @@ -0,0 +1,48 @@ +/* + * 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.support; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.data.repository.init.AbstractRepositoryPopulatorFactoryBean; +import org.springframework.data.repository.init.ResourceReader; +import org.springframework.data.repository.init.ResourceReaderRepositoryPopulator; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * {@link FactoryBean} to set up a {@link ResourceReaderRepositoryPopulator} with a {@link StateMachineJackson2ResourceReader}. + * + * @author Oliver Gierke + * @author Janne Valkealahti + */ +public class StateMachineJackson2RepositoryPopulatorFactoryBean extends AbstractRepositoryPopulatorFactoryBean { + + private ObjectMapper mapper; + + /** + * Configures the {@link ObjectMapper} to be used. + * + * @param mapper the new mapper + */ + public void setMapper(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + protected ResourceReader getResourceReader() { + return new StateMachineJackson2ResourceReader(mapper); + } +} diff --git a/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/support/StateMachineJackson2ResourceReader.java b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/support/StateMachineJackson2ResourceReader.java new file mode 100644 index 00000000..648337ce --- /dev/null +++ b/spring-statemachine-data/src/main/java/org/springframework/statemachine/data/support/StateMachineJackson2ResourceReader.java @@ -0,0 +1,119 @@ +/* + * 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.support; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.core.io.Resource; +import org.springframework.data.repository.init.Jackson2ResourceReader; +import org.springframework.data.repository.init.ResourceReader; +import org.springframework.statemachine.data.BaseRepositoryEntity; +import org.springframework.util.ClassUtils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; + +/** + * A {@link ResourceReader} using Jackson to read JSON into objects. + * + * @author Oliver Gierke + * @author Janne Valkealahti + */ +public class StateMachineJackson2ResourceReader implements ResourceReader { + + /** The Constant DEFAULT_TYPE_KEY. */ + private static final String DEFAULT_TYPE_KEY = "_class"; + + /** The Constant DEFAULT_MAPPER. */ + private static final ObjectMapper DEFAULT_MAPPER = new ObjectMapper(); + + static { + DEFAULT_MAPPER.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + /** The mapper. */ + private final ObjectMapper mapper; + + /** The type key. */ + private String typeKey = DEFAULT_TYPE_KEY; + + /** + * Creates a new {@link Jackson2ResourceReader}. + */ + public StateMachineJackson2ResourceReader() { + this(DEFAULT_MAPPER); + } + + /** + * Creates a new {@link Jackson2ResourceReader} using the given {@link ObjectMapper}. + * + * @param mapper the mapper + */ + public StateMachineJackson2ResourceReader(ObjectMapper mapper) { + this.mapper = mapper == null ? DEFAULT_MAPPER : mapper; + } + + /** + * Configures the JSON document's key to lookup the type to instantiate the object. Defaults to + * {@link Jackson2ResourceReader#DEFAULT_TYPE_KEY}. + * + * @param typeKey the new type key + */ + public void setTypeKey(String typeKey) { + this.typeKey = typeKey; + } + + @Override + public Object readFrom(Resource resource, ClassLoader classLoader) throws Exception { + InputStream stream = resource.getInputStream(); + ObjectReader objectReader = mapper.readerFor(JsonNode.class); + JsonNode node = objectReader.readTree(stream); + objectReader = mapper.readerFor(BaseRepositoryEntity[].class); + + if (node.isArray()) { + List result = new ArrayList(); + BaseRepositoryEntity[] entitys = objectReader.readValue(node); + result.addAll(Arrays.asList(entitys)); + return result; + } + + return readSingle(node, classLoader); + } + + /** + * Reads the given {@link JsonNode} into an instance of the type encoded in it using the configured type key. + * + * @param node must not be {@literal null}. + * @param classLoader the class loader + * @return the object + * @throws IOException Signals that an I/O exception has occurred. + */ + private Object readSingle(JsonNode node, ClassLoader classLoader) throws IOException { + + JsonNode typeNode = node.findValue(typeKey); + String typeName = typeNode == null ? null : typeNode.asText(); + + Class type = ClassUtils.resolveClassName(typeName, classLoader); + return mapper.readerFor(type).readValue(node); + } +} 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 0ecbfadd..ab606940 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 @@ -20,7 +20,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.data.repository.init.Jackson2RepositoryPopulatorFactoryBean; import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.StateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; @@ -30,14 +29,15 @@ import org.springframework.statemachine.data.RepositoryStateMachineModelFactory; import org.springframework.statemachine.data.RepositoryTransition; import org.springframework.statemachine.data.StateRepository; import org.springframework.statemachine.data.TransitionRepository; +import org.springframework.statemachine.data.support.StateMachineJackson2RepositoryPopulatorFactoryBean; @Configuration public class StateMachineConfig { //tag::snippetA[] @Bean - public Jackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { - Jackson2RepositoryPopulatorFactoryBean factoryBean = new Jackson2RepositoryPopulatorFactoryBean(); + public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { + StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[]{new ClassPathResource("data.json")}); return factoryBean; } diff --git a/spring-statemachine-samples/datajpa/src/main/resources/data.json b/spring-statemachine-samples/datajpa/src/main/resources/data.json index b95e9d7f..b0aebabe 100644 --- a/spring-statemachine-samples/datajpa/src/main/resources/data.json +++ b/spring-statemachine-samples/datajpa/src/main/resources/data.json @@ -1,5 +1,6 @@ [ { + "@id": "1", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": true, "state": "S1", @@ -11,6 +12,7 @@ ] }, { + "@id": "2", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S2", @@ -22,6 +24,7 @@ ] }, { + "@id": "3", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S3", @@ -34,15 +37,15 @@ }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S1", - "target": "S2", + "source": "1", + "target": "2", "event": "E1", "kind": "EXTERNAL" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", - "source": "S2", - "target": "S3", + "source": "2", + "target": "3", "event": "E2", "actions": [ {