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 d71b4347..edc0e360 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 @@ -509,45 +509,15 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } S state = stateMachineContext.getState(); boolean stateSet = false; + // handle state reset for (State s : getStates()) { for (State ss : s.getStates()) { if (state != null && ss.getIds().contains(state)) { currentState = s; - - // setting history for 'this' machine - if (history != null && stateMachineContext.getHistoryStates() != null) { - State h = null; - for (State hh : getStates()) { - if (hh.getId().equals(stateMachineContext.getHistoryStates().get(null))) { - h = hh; - break; - } - } - if (h != null) { - ((HistoryPseudoState)history).setState(h); - } - } - // 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(); - - // setting history for submachine state machine - PseudoState submachineHistory = ((AbstractStateMachine)submachine).getHistoryState(); - if (submachineHistory != null) { - State h = null; - for (State hh : submachine.getStates()) { - if (hh.getId().equals(stateMachineContext.getHistoryStates().get(s.getId()))) { - h = hh; - break; - } - } - if (h != null) { - ((HistoryPseudoState)submachineHistory).setState(h); - } - } - for (final StateMachineContext child : stateMachineContext.getChilds()) { submachine.getStateMachineAccessor().doWithRegion(new StateMachineFunction>() { @@ -606,6 +576,41 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo break; } } + + // handle history reset here as above state reset loop breaks out + if (history != null && stateMachineContext.getHistoryStates() != null) { + // setting history for 'this' machine + State h = null; + for (State hh : getStates()) { + if (hh.getId().equals(stateMachineContext.getHistoryStates().get(null))) { + h = hh; + break; + } + } + if (h != null) { + ((HistoryPseudoState) history).setState(h); + } + } + for (State s : getStates()) { + // setting history for 'submachines' + if (s.isSubmachineState()) { + StateMachine submachine = ((AbstractState) s).getSubmachine(); + PseudoState submachineHistory = ((AbstractStateMachine) submachine).getHistoryState(); + if (submachineHistory != null) { + State h = null; + for (State hh : submachine.getStates()) { + if (hh.getId().equals(stateMachineContext.getHistoryStates().get(s.getId()))) { + h = hh; + break; + } + } + if (h != null) { + ((HistoryPseudoState) submachineHistory).setState(h); + } + } + + } + } if (stateSet && stateMachineContext.getExtendedState() != null) { this.extendedState = stateMachineContext.getExtendedState(); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/persist/StateMachinePersistTests2.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/persist/StateMachinePersistTests2.java new file mode 100644 index 00000000..5aedf4e6 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/persist/StateMachinePersistTests2.java @@ -0,0 +1,294 @@ +/* + * 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.persist; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.StateMachineContext; +import org.springframework.statemachine.StateMachinePersist; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.configurers.StateConfigurer; + +public class StateMachinePersistTests2 extends AbstractStateMachineTests { + + // States + public static final String RECORD_AWAITING_CONTENT = "RECORD_AWAITING_CONTENT"; + public static final String RECORD_LOGGING_ACTIVE = "RECORD_LOGGING_ACTIVE"; + public static final String RECORD_LOGGED = "RECORD_LOGGED"; + public static final String RECORD_DISCARDED = "RECORD_DISCARDED"; + public static final String RECORD_LOGGING_ON_HOLD_WITH_ERROR = "RECORD_LOGGING_ON_HOLD_WITH_ERROR"; + public static final String RECORD_LOGGING_IN_PROGRESS = "RECORD_LOGGING_IN_PROGRESS"; + public static final String RECORD_AWAITING_LOGGING = "RECORD_AWAITING_LOGGING"; + public static final String RECORD_LOGGING_ON_HOLD = "RECORD_LOGGING_ON_HOLD"; + public static final String HISTORY = "HISTORY"; + + // Events + public static final String UPLOAD_RECORD = "UPLOAD_RECORD"; + public static final String SUSPEND_RECORD_LOGGING_WITH_ERROR = "SUSPEND_RECORD_LOGGING_WITH_ERROR"; + public static final String DISCARD_RECORD = "DISCARD_RECORD"; + public static final String CANCEL_RECORD_LOGGING = "CANCEL_RECORD_LOGGING"; + public static final String LOG_RECORD = "LOG_RECORD"; + public static final String SUSPEND_RECORD_LOGGING = "SUSPEND_RECORD_LOGGING"; + public static final String START_LOGGING_RECORD = "START_LOGGING_RECORD"; + public static final String RESUME_RECORD_LOGGING = "RESUME_RECORD_LOGGING"; + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @SuppressWarnings("unchecked") + @Test + public void testVariousPersistOperations() throws Exception { + context.register(Config1.class); + context.refresh(); + StateMachinePersist stateMachinePersist = new InMemoryStateMachinePersist(); + StateMachinePersister persister = new DefaultStateMachinePersister( + stateMachinePersist); + + StateMachineFactory factory = context.getBean("LOG_RECORD", StateMachineFactory.class); + StateMachine m = factory.getStateMachine(); + + assertThat(m.getState().getId(), equalTo(RECORD_AWAITING_CONTENT)); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(UPLOAD_RECORD), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[] { RECORD_LOGGING_ACTIVE, RECORD_AWAITING_LOGGING })); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ACTIVE, RECORD_AWAITING_LOGGING})); + persister.persist(m, "xxx"); + + assertThat(m.sendEvent(SUSPEND_RECORD_LOGGING), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ACTIVE, RECORD_LOGGING_ON_HOLD})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ACTIVE, RECORD_LOGGING_ON_HOLD})); + persister.persist(m, "xxx"); + + assertThat(m.sendEvent(START_LOGGING_RECORD), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_IN_PROGRESS})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(CANCEL_RECORD_LOGGING), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ACTIVE, RECORD_LOGGING_ON_HOLD})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(RESUME_RECORD_LOGGING), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ACTIVE, RECORD_AWAITING_LOGGING})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(START_LOGGING_RECORD), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_IN_PROGRESS})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(CANCEL_RECORD_LOGGING), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ACTIVE, RECORD_AWAITING_LOGGING})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(SUSPEND_RECORD_LOGGING_WITH_ERROR), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ON_HOLD_WITH_ERROR})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(RESUME_RECORD_LOGGING), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_ACTIVE, RECORD_AWAITING_LOGGING})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(START_LOGGING_RECORD), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGING_IN_PROGRESS})); + persister.persist(m, "xxx"); + + m = loadStateMachine(factory, persister, "xxx"); + assertThat(m.sendEvent(LOG_RECORD), is(true)); + assertThat(m.getState().getIds(), containsInAnyOrder(new String[]{RECORD_LOGGED})); + persister.persist(m, "xxx"); + } + + private static StateMachine loadStateMachine(StateMachineFactory factory, + StateMachinePersister persister, final String id) throws Exception { + StateMachine stateMachine = factory.getStateMachine(); + persister.restore(stateMachine, id); + return stateMachine; + } + + @Configuration + @EnableStateMachineFactory(name = "LOG_RECORD", contextEvents = false) + static class Config1 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) throws Exception { + config + .withConfiguration() + .autoStartup(true); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states.withStates() + .initial(RECORD_AWAITING_CONTENT) + .state(RECORD_LOGGING_ON_HOLD_WITH_ERROR) + .state(RECORD_LOGGING_ACTIVE) + .state(RECORD_LOGGING_IN_PROGRESS) + .end(RECORD_LOGGED) + .end(RECORD_DISCARDED) + .and() + .withStates() + .parent(RECORD_LOGGING_ACTIVE) + .initial(RECORD_AWAITING_LOGGING) + .state(RECORD_LOGGING_ON_HOLD) + .history(HISTORY, StateConfigurer.History.DEEP); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + /* ****************************************************************************** */ + /* FROM RECORD_AWAITING_CONTENT */ + /* ****************************************************************************** */ + .withExternal() + .source(RECORD_AWAITING_CONTENT) + .event(UPLOAD_RECORD) + .target(RECORD_LOGGING_ACTIVE) + .and() + .withExternal() + .source(RECORD_AWAITING_CONTENT) + .event(SUSPEND_RECORD_LOGGING_WITH_ERROR) + .target(RECORD_LOGGING_ON_HOLD_WITH_ERROR) + .and() + .withExternal() + .source(RECORD_AWAITING_CONTENT) + .event(DISCARD_RECORD) + .target(RECORD_DISCARDED) + .and() + /* ****************************************************************************** */ + /* FROM RECORD_LOGGING_ACTIVE */ + /* ****************************************************************************** */ + .withExternal() + .source(RECORD_LOGGING_ACTIVE) + .event(SUSPEND_RECORD_LOGGING_WITH_ERROR) + .target(RECORD_LOGGING_ON_HOLD_WITH_ERROR) + .and() + .withExternal() + .source(RECORD_LOGGING_ACTIVE) + .event(DISCARD_RECORD) + .target(RECORD_DISCARDED) + .and() + + /* ****************************************************************************** */ + /* FROM RECORD_LOGGING_IN_PROGRESS */ + /* ****************************************************************************** */ + .withExternal() + .source(RECORD_LOGGING_IN_PROGRESS) + .event(CANCEL_RECORD_LOGGING) + .target(HISTORY) + .and() + .withExternal() + .source(RECORD_LOGGING_IN_PROGRESS) + .event(DISCARD_RECORD) + .target(RECORD_DISCARDED) + .and() + .withExternal() + .source(RECORD_LOGGING_IN_PROGRESS) + .event(LOG_RECORD) + .target(RECORD_LOGGED) + .and() + + + /* ****************************************************************************** */ + /* FROM RECORD_AWAITING_LOGGING */ + /* ****************************************************************************** */ + .withExternal() + .source(RECORD_AWAITING_LOGGING) + .event(SUSPEND_RECORD_LOGGING) + .target(RECORD_LOGGING_ON_HOLD) + .and() + .withExternal() + .source(RECORD_AWAITING_LOGGING) + .event(START_LOGGING_RECORD) + .target(RECORD_LOGGING_IN_PROGRESS) + .and() + + + /* ****************************************************************************** */ + /* FROM RECORD_LOGGING_ON_HOLD */ + /* ****************************************************************************** */ + .withExternal() + .source(RECORD_LOGGING_ON_HOLD) + .event(START_LOGGING_RECORD) + .target(RECORD_LOGGING_IN_PROGRESS) + .and() + .withExternal() + .source(RECORD_LOGGING_ON_HOLD) + .event(RESUME_RECORD_LOGGING) + .target(RECORD_AWAITING_LOGGING) + .and() + + + /* ****************************************************************************** */ + /* FROM RECORD_LOGGING_ON_HOLD_WITH_ERROR */ + /* ****************************************************************************** */ + .withExternal() + .source(RECORD_LOGGING_ON_HOLD_WITH_ERROR) + .event(RESUME_RECORD_LOGGING) + .target(HISTORY) + .and() + .withExternal() + .source(RECORD_LOGGING_ON_HOLD_WITH_ERROR) + .event(DISCARD_RECORD) + .target(RECORD_DISCARDED) + .and(); + } + } + + static class InMemoryStateMachinePersist implements StateMachinePersist { + private final Map> contexts = new HashMap<>(); + + @Override + public void write(StateMachineContext stateMachineContext, String contextOjb) throws Exception { + contexts.put(contextOjb,stateMachineContext); + } + + @Override + public StateMachineContext read(String contextOjb) throws Exception { + return contexts.get(contextOjb); + } + } +}