diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index 45572f50..4ea91039 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -458,17 +458,40 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo @Override public void resetStateMachine(StateMachineContext stateMachineContext) { + if (log.isDebugEnabled()) { + log.debug("Request to reset state machine: stateMachine=[" + this + "] stateMachineContext=[" + stateMachineContext + "]"); + } S state = stateMachineContext.getState(); boolean stateSet = false; for (State s : getStates()) { for (State ss : s.getStates()) { if (ss.getIds().contains(state)) { currentState = s; - // TODO: not sure about starting submachine here, though - // needed if we only transit to super state + // TODO: not sure about starting submachine/regions here, though + // needed if we only transit to super state or reset regions if (s.isSubmachineState()) { StateMachine submachine = ((AbstractState)s).getSubmachine(); submachine.start(); + } else if (s.isOrthogonal() && stateMachineContext.getChilds() != null) { + Collection> regions = ((AbstractState)s).getRegions(); + for (Region region : regions) { + for (final StateMachineContext child : stateMachineContext.getChilds()) { + ((StateMachine)region).getStateMachineAccessor().doWithRegion(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess function) { + function.resetStateMachine(child); + } + }); + } + } + for (Region region : regions) { + region.start(); + } + } + + if (log.isDebugEnabled()) { + log.debug("State reseted: stateMachine=[" + this + "] stateMachineContext=[" + stateMachineContext + "]"); } stateSet = true; break; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineResetTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineResetTests.java index e6ea9fce..492e5094 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineResetTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineResetTests.java @@ -19,7 +19,9 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; @@ -126,6 +128,37 @@ public class StateMachineResetTests extends AbstractStateMachineTests { assertThat((Integer)machine.getExtendedState().getVariables().get("foo"), is(1)); } + @Test + public void testResetRegions() { + context.register(Config2.class); + context.refresh(); + @SuppressWarnings("unchecked") + StateMachine machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + + DefaultStateMachineContext stateMachineContext1 = + new DefaultStateMachineContext(TestStates.S21, TestEvents.E2, null, null); + DefaultStateMachineContext stateMachineContext2 = + new DefaultStateMachineContext(TestStates.S31, TestEvents.E3, null, null); + + List> childs = new ArrayList>(); + childs.add(stateMachineContext1); + childs.add(stateMachineContext2); + + DefaultStateMachineContext stateMachineContext = + new DefaultStateMachineContext(childs, TestStates.S2, TestEvents.E1, null, null); + + machine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess function) { + function.resetStateMachine(stateMachineContext); + } + }); + + machine.start(); + assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S21, TestStates.S31)); + } + @Configuration @EnableStateMachine static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -286,4 +319,51 @@ public class StateMachineResetTests extends AbstractStateMachineTests { } } + @Configuration + @EnableStateMachine + static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .state(TestStates.SI) + .state(TestStates.S2) + .end(TestStates.SF) + .and() + .withStates() + .parent(TestStates.S2) + .initial(TestStates.S20) + .state(TestStates.S20) + .state(TestStates.S21) + .and() + .withStates() + .parent(TestStates.S2) + .initial(TestStates.S30) + .state(TestStates.S30) + .state(TestStates.S31); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S2) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S20) + .target(TestStates.S21) + .event(TestEvents.E2) + .and() + .withExternal() + .source(TestStates.S30) + .target(TestStates.S31) + .event(TestEvents.E3); + } + + } + } diff --git a/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/PersistStateMachineHandlerTests.java b/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/PersistStateMachineHandlerTests.java new file mode 100644 index 00000000..6ca6efe7 --- /dev/null +++ b/spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/PersistStateMachineHandlerTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 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.recipes; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineBuilder; +import org.springframework.statemachine.recipes.persist.PersistStateMachineHandler; +import org.springframework.statemachine.recipes.persist.PersistStateMachineHandler.PersistStateChangeListener; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; + +public class PersistStateMachineHandlerTests { + + @Test + public void testStateChangeViaPersist() throws Exception { + StateMachine stateMachine = buildTestStateMachine(); + + PersistStateMachineHandler handler = new PersistStateMachineHandler(stateMachine); + handler.afterPropertiesSet(); + handler.start(); + + TestPersistStateChangeListener listener = new TestPersistStateChangeListener(); + handler.addPersistStateChangeListener(listener); + + Message event = MessageBuilder.withPayload("E2").build(); + handler.handleEventWithState(event, "S1"); + assertThat(listener.latch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2")); + } + + private static class TestPersistStateChangeListener implements PersistStateChangeListener { + + CountDownLatch latch = new CountDownLatch(1); + + @Override + public void onPersist(State state, Message message, + Transition transition, StateMachine stateMachine) { + latch.countDown(); + } + + } + + private static StateMachine buildTestStateMachine() + throws Exception { + StateMachineBuilder.Builder builder = StateMachineBuilder.builder(); + + builder.configureConfiguration() + .withConfiguration() + .taskExecutor(new SyncTaskExecutor()) + .autoStartup(true); + + builder.configureStates() + .withStates() + .initial("SI") + .state("S1") + .state("S2"); + + builder.configureTransitions() + .withExternal() + .source("SI").target("S1").event("E1") + .and() + .withExternal() + .source("S1").target("S2").event("E2"); + + return builder.build(); + } + +}