diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java index b500fbfe..4802f774 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -46,7 +46,9 @@ import org.springframework.statemachine.state.RegionState; import org.springframework.statemachine.state.State; import org.springframework.statemachine.state.StateMachineState; import org.springframework.statemachine.support.DefaultExtendedState; +import org.springframework.statemachine.support.StateMachineFunction; import org.springframework.statemachine.support.LifecycleObjectSupport; +import org.springframework.statemachine.support.StateMachineAccess; import org.springframework.statemachine.support.tree.Tree; import org.springframework.statemachine.support.tree.Tree.Node; import org.springframework.statemachine.support.tree.TreeTraverser; @@ -181,6 +183,16 @@ public class EnumStateMachineFactory, E extends Enum> exten stateStack.push(stateData); } + // set top-level machine as relay + final StateMachine mm = machine; + ((StateMachineAccess)machine).doWithAllRegions(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess stateMachineAccess) { + stateMachineAccess.setRelay(mm); + } + }); + return machine; } 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 428bbb36..2304fb1e 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 @@ -76,7 +76,7 @@ import org.springframework.util.StringUtils; * @param the type of state * @param the type of event */ -public abstract class AbstractStateMachine extends StateMachineObjectSupport implements StateMachine { +public abstract class AbstractStateMachine extends StateMachineObjectSupport implements StateMachine, StateMachineAccess { private static final Log log = LogFactory.getLog(AbstractStateMachine.class); @@ -114,6 +114,8 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo private final List> triggerlessTransitions = new ArrayList>(); + private StateMachine relay; + /** * Instantiates a new abstract state machine. * @@ -235,12 +237,12 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } registerTriggerListener(); registerPseudoStateListener(); - switchToState(initialState, initialEvent, null, this); + switchToState(initialState, initialEvent, null, getRelayStateMachine()); // TODO: it is a bit off to call handlers after switchToState for initial state callHandlers(null, initialState, initialEvent); // TODO: for now execute outside of switchToState if (initialTransition != null) { - StateContext stateContext = buildStateContext(initialEvent, initialTransition, this); + StateContext stateContext = buildStateContext(initialEvent, initialTransition, getRelayStateMachine()); initialTransition.transit(stateContext); } notifyStateMachineStarted(this); @@ -288,6 +290,33 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo return transitions; } + @Override + public void doWithAllRegions(StateMachineFunction> stateMachineAccess) { + stateMachineAccess.apply(this); + for (State state : states) { + if (state.isSubmachineState()) { + StateMachine submachine = ((AbstractState)state).getSubmachine(); + if (submachine instanceof StateMachineAccess) { + ((StateMachineAccess)submachine).doWithAllRegions(stateMachineAccess); + } + } else if (state.isOrthogonal()) { + Collection> regions = ((AbstractState)state).getRegions(); + for (Region region : regions) { + ((StateMachineAccess)region).doWithAllRegions(stateMachineAccess); + } + } + } + } + + @Override + public void setRelay(StateMachine stateMachine) { + this.relay = stateMachine; + } + + private StateMachine getRelayStateMachine() { + return relay != null ? relay : this; + } + @Override public String toString() { ArrayList> all = new ArrayList>(); @@ -372,9 +401,9 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo public void onContext(PseudoStateContext context) { PseudoState pseudoState = context.getPseudoState(); State toState = findStateWithPseudoState(pseudoState); - StateContext stateContext = buildStateContext(null, null, AbstractStateMachine.this); + StateContext stateContext = buildStateContext(null, null, getRelayStateMachine()); pseudoState.exit(stateContext); - switchToState(toState, null, null, AbstractStateMachine.this); + switchToState(toState, null, null, getRelayStateMachine()); } }); } @@ -689,7 +718,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo private void handleTriggerTrans(List> trans, Message queuedMessage) { for (Transition t : trans) { - StateContext stateContext = buildStateContext(queuedMessage, t, this); + StateContext stateContext = buildStateContext(queuedMessage, t, getRelayStateMachine()); if (t == null) { continue; } @@ -708,7 +737,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo notifyTransitionStart(t); callHandlers(t.getSource(), t.getTarget(), queuedMessage); if (t.getKind() != TransitionKind.INTERNAL) { - switchToState(t.getTarget(), queuedMessage, t, this); + switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine()); } notifyTransition(t); notifyTransitionEnd(t); @@ -720,7 +749,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } private void callHandlers(State sourceState, State targetState, Message message) { - StateContext stateContext = buildStateContext(message, null, this); + StateContext stateContext = buildStateContext(message, null, getRelayStateMachine()); getStateMachineHandlerResults(getStateMachineHandlers(sourceState, targetState), stateContext); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineAccess.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineAccess.java new file mode 100644 index 00000000..e0d943d5 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineAccess.java @@ -0,0 +1,45 @@ +/* + * 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.support; + +import org.springframework.statemachine.StateMachine; + +/** + * Functional interface for {@link StateMachine} to allow more programmetic + * access to underlying functionality. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachineAccess { + + /** + * Execute given {@link StateMachineFunction} with all recursive regions. + * + * @param stateMachineAccess the state machine access + */ + void doWithAllRegions(StateMachineFunction> stateMachineAccess); + + /** + * Sets the relay state machine. + * + * @param stateMachine the state machine + */ + void setRelay(StateMachine stateMachine); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineFunction.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineFunction.java new file mode 100644 index 00000000..eec0e639 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineFunction.java @@ -0,0 +1,36 @@ +/* + * 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.support; + +/** + * Strategic function interface for applying arbitrary function + * or feature. + * + * @author Janne Valkealahti + * + * @param the function type + * @see StateMachineAccess + */ +public interface StateMachineFunction { + + /** + * Apply a function. + * + * @param function the function + */ + void apply(I function); + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/RelayTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/RelayTests.java new file mode 100644 index 00000000..44dbde62 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/RelayTests.java @@ -0,0 +1,97 @@ +/* + * 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; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +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.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +public class RelayTests extends AbstractStateMachineTests { + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @Test + @SuppressWarnings("unchecked") + public void testRelayFromSubmachine() throws Exception { + context.register(Config1.class); + context.refresh(); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + machine.start(); + machine.sendEvent(TestEvents.E1); + + assertThat(machine.getState().getIds(), contains(TestStates.S2, TestStates.S21)); + } + + @Configuration + @EnableStateMachine + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S2) + .and() + .withStates() + .parent(TestStates.S2) + .initial(TestStates.S20) + .state(TestStates.S20, action1(), null) + .state(TestStates.S21); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S20) + .target(TestStates.S21) + .event(TestEvents.E2); + } + + @Bean + public Action action1() { + return new Action() { + + @Override + public void execute(StateContext context) { + context.getStateMachine().sendEvent(TestEvents.E2); + } + }; + } + } + +}