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 9f235fe9..6f7979b1 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 @@ -737,7 +737,8 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } } if (stateSet && stateMachineContext.getExtendedState() != null) { - this.extendedState = stateMachineContext.getExtendedState(); + this.extendedState.getVariables().clear(); + this.extendedState.getVariables().putAll(stateMachineContext.getExtendedState().getVariables()); } if (currentState instanceof Lifecycle) { ((Lifecycle)currentState).start(); 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 2e3ba6e7..6dc55bbf 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -29,6 +29,7 @@ import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.StateContext.Stage; import org.springframework.statemachine.access.StateMachineAccess; import org.springframework.statemachine.access.StateMachineFunction; import org.springframework.statemachine.action.Action; @@ -40,6 +41,7 @@ import org.springframework.statemachine.config.builders.StateMachineConfiguratio import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.support.DefaultExtendedState; import org.springframework.statemachine.support.DefaultStateMachineContext; @@ -278,6 +280,70 @@ public class StateMachineResetTests extends AbstractStateMachineTests { } + @Test + public void testResetKeepsExtendedStateIntactInSubmachine() { + context.register(Config5.class); + context.refresh(); + @SuppressWarnings("unchecked") + StateMachine machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + CountListener listener = new CountListener(); + machine.addStateListener(listener); + + assertThat((Integer)machine.getExtendedState().getVariables().get("count1"), nullValue()); + assertThat(listener.count1, nullValue()); + assertThat(listener.count2, nullValue()); + machine.sendEvent(Events.A); + assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1, States.S11)); + assertThat((Integer)machine.getExtendedState().getVariables().get("count1"), is(1)); + assertThat(listener.count1, is(1)); + // listener is called before action is executed + assertThat(listener.count2, nullValue()); + + assertThat((Integer)machine.getExtendedState().getVariables().get("count2"), nullValue()); + machine.sendEvent(Events.B); + assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1, States.S12)); + assertThat((Integer)machine.getExtendedState().getVariables().get("count2"), is(1)); + assertThat(listener.count1, is(1)); + assertThat(listener.count2, nullValue()); + + machine.sendEvent(Events.C); + assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0)); + assertThat(listener.count1, is(1)); + assertThat(listener.count2, is(1)); + + machine.stop(); + Map variables = new HashMap(); + variables.putAll(machine.getExtendedState().getVariables()); + ExtendedState extendedState = new DefaultExtendedState(variables); + DefaultStateMachineContext stateMachineContext = new DefaultStateMachineContext(States.S0, null, null, extendedState); + + machine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess function) { + function.resetStateMachine(stateMachineContext); + } + }); + machine.start(); + + assertThat((Integer)machine.getExtendedState().getVariables().get("count1"), is(1)); + assertThat(listener.count1, is(1)); + assertThat(listener.count2, is(1)); + machine.sendEvent(Events.A); + assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1, States.S11)); + assertThat((Integer)machine.getExtendedState().getVariables().get("count1"), is(2)); + assertThat(listener.count1, is(2)); + // listener is called before action is executed + assertThat(listener.count2, is(1)); + + assertThat((Integer)machine.getExtendedState().getVariables().get("count2"), is(1)); + machine.sendEvent(Events.B); + assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1, States.S12)); + assertThat((Integer)machine.getExtendedState().getVariables().get("count2"), is(2)); + assertThat(listener.count1, is(2)); + assertThat(listener.count2, is(1)); + } + @Configuration @EnableStateMachine static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -477,6 +543,100 @@ public class StateMachineResetTests extends AbstractStateMachineTests { } } + @Configuration + @EnableStateMachine + static class Config5 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true); + } + + @Override + public void configure(StateMachineStateConfigurer states) + throws Exception { + states + .withStates() + .initial(States.S0) + .stateEntry(States.S1, updateAction1()) + .and() + .withStates() + .parent(States.S1) + .initial(States.S11) + .stateEntry(States.S12, updateAction2()); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) + throws Exception { + transitions + .withExternal() + .source(States.S0) + .target(States.S1) + .event(Events.A) + .and() + .withExternal() + .source(States.S11) + .target(States.S12) + .event(Events.B) + .and() + .withExternal() + .source(States.S1) + .target(States.S0) + .event(Events.C); + } + + @Bean + public Action updateAction1() { + return new Action() { + + @Override + public void execute(StateContext context) { + Integer count = context.getExtendedState().get("count1", Integer.class); + if (count == null) { + context.getExtendedState().getVariables().put("count1", 1); + } else { + context.getExtendedState().getVariables().put("count1", (count + 1)); + } + } + }; + } + + @Bean + public Action updateAction2() { + return new Action() { + + @Override + public void execute(StateContext context) { + Integer count = context.getExtendedState().get("count2", Integer.class); + if (count == null) { + context.getExtendedState().getVariables().put("count2", 1); + } else { + context.getExtendedState().getVariables().put("count2", (count + 1)); + } + } + }; + } + } + + private static class CountListener extends StateMachineListenerAdapter { + + Integer count1; + Integer count2; + + @Override + public void stateContext(StateContext stateContext) { + if (stateContext.getStage() == Stage.STATE_ENTRY) { + ExtendedState extendedState = stateContext.getExtendedState(); + count1 = extendedState.get("count1", Integer.class); + count2 = extendedState.get("count2", Integer.class); + } + } + } + public static enum States { S0, S1, S11, S12, S2, S21, S211, S212 }