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 025b7461..15ff6baa 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 @@ -357,6 +357,74 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { plan.test(); } + @SuppressWarnings("unchecked") + @Test + public void testMachine8First() throws Exception { + context.register(Config8.class, FactoryConfig.class); + context.refresh(); + StateMachineFactory stateMachineFactory = context.getBean(StateMachineFactory.class); + StateMachine stateMachine = stateMachineFactory.getStateMachine(); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectStates("SI").and() + .step().sendEvent(MessageBuilder.withPayload("E1").setHeader("junction", "s30").build()).expectStates("S30").and() + .build(); + plan.test(); + } + + @SuppressWarnings("unchecked") + @Test + public void testMachine8Then1() throws Exception { + context.register(Config8.class, FactoryConfig.class); + context.refresh(); + StateMachineFactory stateMachineFactory = context.getBean(StateMachineFactory.class); + StateMachine stateMachine = stateMachineFactory.getStateMachine(); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectStates("SI").and() + .step().sendEvent(MessageBuilder.withPayload("E1").setHeader("junction", "s31").build()).expectStates("S31").and() + .build(); + plan.test(); + } + + @SuppressWarnings("unchecked") + @Test + public void testMachine8Then2() throws Exception { + context.register(Config8.class, FactoryConfig.class); + context.refresh(); + StateMachineFactory stateMachineFactory = context.getBean(StateMachineFactory.class); + StateMachine stateMachine = stateMachineFactory.getStateMachine(); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectStates("SI").and() + .step().sendEvent(MessageBuilder.withPayload("E1").setHeader("junction", "s32").build()).expectStates("S32").and() + .build(); + plan.test(); + } + + @SuppressWarnings("unchecked") + @Test + public void testMachine8Last() throws Exception { + context.register(Config8.class, FactoryConfig.class); + context.refresh(); + StateMachineFactory stateMachineFactory = context.getBean(StateMachineFactory.class); + StateMachine stateMachine = stateMachineFactory.getStateMachine(); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectStates("SI").and() + .step().sendEvent(MessageBuilder.withPayload("E1").build()).expectStates("S33").and() + .build(); + plan.test(); + } + @Test public void testPopulate1() { context.register(Config2.class); @@ -500,6 +568,33 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { } } + @EnableAutoConfiguration + static class Config8 { + + @Bean + public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { + StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); + factoryBean.setResources(new Resource[]{new ClassPathResource("data8.json")}); + return factoryBean; + } + + @Bean + public Guard s30Guard() { + return new JunctionGuard("s30"); + } + + @Bean + public Guard s31Guard() { + return new JunctionGuard("s31"); + } + + @Bean + public Guard s32Guard() { + return new JunctionGuard("s32"); + } + + } + @Configuration @EnableStateMachineFactory public static class FactoryConfig extends StateMachineConfigurerAdapter { @@ -537,4 +632,17 @@ public class JpaRepositoryTests extends AbstractJpaRepositoryTests { } } + private static class JunctionGuard implements Guard { + + private final String match; + + public JunctionGuard(String match) { + this.match = match; + } + + @Override + public boolean evaluate(StateContext context) { + return ObjectUtils.nullSafeEquals(match, context.getMessageHeaders().get("junction", String.class)); + } + } } diff --git a/spring-statemachine-data/jpa/src/test/resources/data8.json b/spring-statemachine-data/jpa/src/test/resources/data8.json new file mode 100644 index 00000000..95f0d518 --- /dev/null +++ b/spring-statemachine-data/jpa/src/test/resources/data8.json @@ -0,0 +1,84 @@ +[ + { + "@id": "1", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": true, + "state": "SI" + }, + { + "@id": "2", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": false, + "state": "S3", + "kind": "JUNCTION" + }, + { + "@id": "3", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": false, + "state": "S30" + }, + { + "@id": "4", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": false, + "state": "S31" + }, + { + "@id": "5", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": false, + "state": "S32" + }, + { + "@id": "6", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", + "initial": false, + "state": "S33" + }, + { + "@id": "7", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryGuard", + "name": "s30Guard" + }, + { + "@id": "8", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryGuard", + "name": "s31Guard" + }, + { + "@id": "9", + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryGuard", + "name": "s32Guard" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", + "source": "1", + "target": "2", + "event": "E1", + "kind": "EXTERNAL" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", + "source": "2", + "target": "3", + "guard": "7" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", + "source": "2", + "target": "4", + "guard": "8" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", + "source": "2", + "target": "5", + "guard": "9" + }, + { + "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", + "source": "2", + "target": "6" + } +] 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 132d3b53..8f0e2964 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 @@ -33,6 +33,7 @@ import org.springframework.statemachine.config.model.ChoiceData; import org.springframework.statemachine.config.model.ConfigurationData; import org.springframework.statemachine.config.model.DefaultStateMachineModel; import org.springframework.statemachine.config.model.HistoryData; +import org.springframework.statemachine.config.model.JunctionData; import org.springframework.statemachine.config.model.StateData; import org.springframework.statemachine.config.model.StateMachineModel; import org.springframework.statemachine.config.model.StateMachineModelFactory; @@ -155,6 +156,7 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode Collection> transitionData = new ArrayList<>(); Collection> historys = new ArrayList>(); Map>> choices = new HashMap>>(); + Map>> junctions = new HashMap>>(); for (RepositoryTransition t : transitionRepository.findByMachineId(machineId)) { Collection> actions = new ArrayList>(); @@ -194,7 +196,19 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode } else { list.addFirst(new ChoiceData(t.getSource().getState(), t.getTarget().getState(), guard)); } - + } else if (t.getSource().getKind() == PseudoStateKind.JUNCTION) { + LinkedList> list = junctions.get(t.getSource().getState()); + if (list == null) { + list = new LinkedList>(); + junctions.put(t.getSource().getState(), list); + } + guard = resolveGuard(t); + // we want null guards to be at the end + if (guard == null) { + list.addLast(new JunctionData(t.getSource().getState(), t.getTarget().getState(), guard)); + } else { + list.addFirst(new JunctionData(t.getSource().getState(), t.getTarget().getState(), guard)); + } } else if (t.getSource().getKind() == PseudoStateKind.HISTORY_SHALLOW) { historys.add(new HistoryData(t.getSource().getState(), t.getTarget().getState())); } else if (t.getSource().getKind() == PseudoStateKind.HISTORY_DEEP) { @@ -204,8 +218,10 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode HashMap>> choicesCopy = new HashMap>>(); choicesCopy.putAll(choices); + HashMap>> junctionsCopy = new HashMap>>(); + junctionsCopy.putAll(junctions); - TransitionsData transitionsData = new TransitionsData<>(transitionData, choicesCopy, null, null, null, null, null, historys); + TransitionsData transitionsData = new TransitionsData<>(transitionData, choicesCopy, junctionsCopy, null, null, null, null, historys); StateMachineModel stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData); return stateMachineModel;