diff --git a/build.gradle b/build.gradle index 53399c87..b19cb8c0 100644 --- a/build.gradle +++ b/build.gradle @@ -283,7 +283,7 @@ configure(rootProject) { idseparator: '-', doctype: 'book', numbered: '', - 'spring-hadoop-version' : project.version, + 'spring-statemachine-version' : project.version, 'spring-version' : springVersion, revnumber : project.version } diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc index 4f7881ab..b987d7b1 100644 --- a/docs/src/reference/asciidoc/index.adoc +++ b/docs/src/reference/asciidoc/index.adoc @@ -9,6 +9,7 @@ :core-dao-exceptions: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/dao.html#dao-exceptions :core-jdbc: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html :core-jdbc-JdbcTemplate: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate +:sm-statecontext: http://docs.spring.io/spring-statemachine/docs/{spring-statemachine-version}/api/org/springframework/statemachine/StateContext.html = Spring Statemachine - Reference Documentation diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 48ec6da3..8dfd2a6d 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -633,7 +633,7 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippet6] [[sm-statecontext]] == Using StateContext -_StateContext_ is a domain object representing a current status of a +{sm-statecontext}[_StateContext_] is a domain object representing a current status of a state machine within a transition or an action. Context gives an access to a various information like event, message headers, extended state variables, current transition and a top-level state machine in diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java index 318dd8e1..a1dc7407 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java @@ -37,6 +37,13 @@ import org.springframework.statemachine.transition.Transition; */ public interface StateContext { + /** + * Gets the stage this context is attached. + * + * @return the stage + */ + Stage getStage(); + /** * Gets the message associated with a context. Message may be null if transition * is not triggered by a signal. @@ -115,4 +122,21 @@ public interface StateContext { */ Exception getException(); + /** + * Enumeration of possible stages context is attached. + */ + public static enum Stage { + EVENT_NOT_ACCEPTED, + EXTENDED_STATE_CHANGED, + STATE_CHANGED, + STATE_ENTRY, + STATE_EXIT, + STATEMACHINE_ERROR, + STATEMACHINE_START, + STATEMACHINE_STOP, + TRANSITION, + TRANSITION_START, + TRANSITION_END; + } + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEvent.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEvent.java index 703344fa..5649f4d9 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEvent.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/StateMachineEvent.java @@ -38,7 +38,7 @@ public abstract class StateMachineEvent extends ApplicationEvent { @Override public String toString() { - return "AbstractStateMachineEvent [source=" + source + "]"; + return "StateMachineEvent [source=" + source + "]"; } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/TransitionEvent.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/TransitionEvent.java index b85f235b..84ed942a 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/TransitionEvent.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/event/TransitionEvent.java @@ -50,7 +50,7 @@ public abstract class TransitionEvent extends StateMachineEvent { @Override public String toString() { - return "OnTransitionStartEvent [transition=" + transition + "]"; + return "TransitionEvent [transition=" + transition + "]"; } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java index 67d80de4..fda7ad3f 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/CompositeStateMachineListener.java @@ -18,6 +18,7 @@ package org.springframework.statemachine.listener; import java.util.Iterator; import org.springframework.messaging.Message; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -30,8 +31,8 @@ import org.springframework.statemachine.transition.Transition; * @param the type of state * @param the type of event */ -public class CompositeStateMachineListener extends AbstractCompositeListener> implements - StateMachineListener { +public class CompositeStateMachineListener extends AbstractCompositeListener> + implements StateMachineListener { @Override public void stateChanged(State from, State to) { @@ -121,4 +122,12 @@ public class CompositeStateMachineListener extends AbstractCompositeListene } } + @Override + public void stateContext(StateContext stateContext) { + for (Iterator> iterator = getListeners().reverse(); iterator.hasNext();) { + StateMachineListener listener = iterator.next(); + listener.stateContext(stateContext); + } + } + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java index 40490a1b..f22c6a86 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListener.java @@ -16,6 +16,8 @@ package org.springframework.statemachine.listener; import org.springframework.messaging.Message; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateContext.Stage; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -110,4 +112,11 @@ public interface StateMachineListener { */ void extendedStateChanged(Object key, Object value); + /** + * Notified on various {@link Stage}s about a {@link StateContext}. + * + * @param stateContext the state context + */ + void stateContext(StateContext stateContext); + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java index 233ea181..ec035076 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/listener/StateMachineListenerAdapter.java @@ -16,6 +16,7 @@ package org.springframework.statemachine.listener; import org.springframework.messaging.Message; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; @@ -75,4 +76,8 @@ public class StateMachineListenerAdapter implements StateMachineListener stateContext) { + } + } 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 722d2db1..215ba6a8 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 @@ -33,6 +33,7 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.ExtendedState; import org.springframework.statemachine.ExtendedState.ExtendedStateChangeListener; +import org.springframework.statemachine.StateContext.Stage; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.StateMachineContext; @@ -176,7 +177,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo public boolean sendEvent(Message event) { if (hasStateMachineError()) { // TODO: should we throw exception? - notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine())); + notifyEventNotAccepted(event, buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine())); return false; } @@ -184,18 +185,18 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo event = getStateMachineInterceptors().preEvent(event, this); } catch (Exception e) { log.info("Event " + event + " threw exception in interceptors, not accepting event"); - notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine())); + notifyEventNotAccepted(event, buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine())); return false; } if (isComplete() || !isRunning()) { - notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine())); + notifyEventNotAccepted(event, buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine())); return false; } boolean accepted = acceptEvent(event); stateMachineExecutor.execute(); if (!accepted) { - notifyEventNotAccepted(event, buildStateContext(event, null, getRelayStateMachine())); + notifyEventNotAccepted(event, buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine())); } return accepted; } @@ -216,7 +217,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo extendedState.setExtendedStateChangeListener(new ExtendedStateChangeListener() { @Override public void changed(Object key, Object value) { - notifyExtendedStateChanged(key, value, buildStateContext(null, null, getRelayStateMachine())); + notifyExtendedStateChanged(key, value, buildStateContext(Stage.EXTENDED_STATE_CHANGED, null, null, getRelayStateMachine())); } }); @@ -261,17 +262,17 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo @Override public void transit(Transition t, StateContext stateContext, Message queuedMessage) { - StateContext stateContext2 = buildStateContext(queuedMessage, null, getRelayStateMachine()); - notifyTransitionStart(t, queuedMessage, buildStateContext(queuedMessage, null, getRelayStateMachine())); - notifyTransition(t, queuedMessage, buildStateContext(queuedMessage, null, getRelayStateMachine())); + // TODO: fix above stateContext as it's not used + notifyTransitionStart(t, queuedMessage, buildStateContext(Stage.TRANSITION_START, queuedMessage, null, getRelayStateMachine())); + notifyTransition(t, queuedMessage, buildStateContext(Stage.TRANSITION, queuedMessage, null, getRelayStateMachine())); if (t.getKind() == TransitionKind.INITIAL) { switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine()); - notifyStateMachineStarted(getRelayStateMachine(), stateContext2); + notifyStateMachineStarted(getRelayStateMachine(), buildStateContext(Stage.STATEMACHINE_START, queuedMessage, null, getRelayStateMachine())); } else if (t.getKind() != TransitionKind.INTERNAL) { switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine()); } // TODO: looks like events should be called here and anno processing earlier - notifyTransitionEnd(t, queuedMessage, buildStateContext(queuedMessage, null, getRelayStateMachine())); + notifyTransitionEnd(t, queuedMessage, buildStateContext(Stage.TRANSITION_END, queuedMessage, null, getRelayStateMachine())); } }); stateMachineExecutor = executor; @@ -303,7 +304,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo // assume that state was set/reseted so we need to // dispatch started event which would net getting // dispatched via executor - StateContext stateContext = buildStateContext(null, null, getRelayStateMachine()); + StateContext stateContext = buildStateContext(Stage.STATEMACHINE_START, null, null, getRelayStateMachine()); notifyStateMachineStarted(getRelayStateMachine(), stateContext); return; } @@ -325,7 +326,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo @Override protected void doStop() { stateMachineExecutor.stop(); - notifyStateMachineStopped(this, buildStateContext(null, null, this)); + notifyStateMachineStopped(this, buildStateContext(Stage.STATEMACHINE_STOP, null, null, this)); currentState = null; initialEnabled = null; } @@ -346,7 +347,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo currentError = exception; } if (currentError != null) { - notifyStateMachineError(this, currentError, buildStateContext(null, null, this, currentError)); + notifyStateMachineError(this, currentError, buildStateContext(Stage.STATEMACHINE_ERROR, null, null, this, currentError)); } } @@ -624,7 +625,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo PseudoStateKind kind = state.getPseudoState() != null ? state.getPseudoState().getKind() : null; if (kind == PseudoStateKind.CHOICE || kind == PseudoStateKind.HISTORY_SHALLOW || kind == PseudoStateKind.HISTORY_DEEP) { - StateContext stateContext = buildStateContext(message, transition, stateMachine); + StateContext stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine); State toState = state.getPseudoState().entry(stateContext); if (kind == PseudoStateKind.CHOICE) { @@ -659,7 +660,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo public void onContext(PseudoStateContext context) { PseudoState pseudoState = context.getPseudoState(); State toState = findStateWithPseudoState(pseudoState); - StateContext stateContext = buildStateContext(null, null, getRelayStateMachine()); + StateContext stateContext = buildStateContext(Stage.STATE_EXIT, null, null, getRelayStateMachine()); pseudoState.exit(stateContext); switchToState(toState, null, null, getRelayStateMachine()); } @@ -677,22 +678,22 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo return null; } - private StateContext buildStateContext(Message message, Transition transition, StateMachine stateMachine) { + private StateContext buildStateContext(Stage stage, Message message, Transition transition, StateMachine stateMachine) { MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders( new HashMap()); - return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine); + return new DefaultStateContext(stage, message, messageHeaders, extendedState, transition, stateMachine, null, null, null); } - private StateContext buildStateContext(Message message, Transition transition, StateMachine stateMachine, Exception exception) { + private StateContext buildStateContext(Stage stage, Message message, Transition transition, StateMachine stateMachine, Exception exception) { MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders( new HashMap()); - return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine, null, null, exception); + return new DefaultStateContext(stage, message, messageHeaders, extendedState, transition, stateMachine, null, null, exception); } - private StateContext buildStateContext(Message message, Transition transition, StateMachine stateMachine, State source, State target) { + private StateContext buildStateContext(Stage stage, Message message, Transition transition, StateMachine stateMachine, State source, State target) { MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders( new HashMap()); - return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine, source, target, null); + return new DefaultStateContext(stage, message, messageHeaders, extendedState, transition, stateMachine, source, target, null); } private State findDeepParent(State state) { @@ -727,7 +728,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo start(); } entryToState(state, message, transition, stateMachine); - notifyStateChanged(notifyFrom, state, message, buildStateContext(message, null, getRelayStateMachine(), notifyFrom, state)); + notifyStateChanged(notifyFrom, state, message, buildStateContext(Stage.STATE_CHANGED, message, null, getRelayStateMachine(), notifyFrom, state)); nonDeepStatePresent = true; } else if (currentState == null && StateMachineUtils.isSubstate(findDeep, state)) { if (exit) { @@ -739,7 +740,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo start(); } entryToState(findDeep, message, transition, stateMachine); - notifyStateChanged(notifyFrom, findDeep, message, buildStateContext(message, null, getRelayStateMachine(), notifyFrom, findDeep)); + notifyStateChanged(notifyFrom, findDeep, message, buildStateContext(Stage.STATE_CHANGED, message, null, getRelayStateMachine(), notifyFrom, findDeep)); } if (currentState != null && !nonDeepStatePresent) { @@ -826,7 +827,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo return; } log.trace("Trying Exit state=[" + state + "]"); - StateContext stateContext = buildStateContext(message, transition, stateMachine); + StateContext stateContext = buildStateContext(Stage.STATE_EXIT, message, transition, stateMachine); if (transition != null) { @@ -853,7 +854,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo log.debug("Exit state=[" + state + "]"); state.exit(stateContext); - notifyStateExited(state, message, buildStateContext(message, null, getRelayStateMachine(), state, null)); + notifyStateExited(state, message, buildStateContext(Stage.STATE_EXIT, message, null, getRelayStateMachine(), state, null)); } private void entryToState(State state, Message message, Transition transition, StateMachine stateMachine) { @@ -861,7 +862,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo return; } log.trace("Trying Enter state=[" + state + "]"); - StateContext stateContext = buildStateContext(message, transition, stateMachine); + StateContext stateContext = buildStateContext(Stage.STATE_ENTRY, message, transition, stateMachine); if (transition != null) { State findDeep1 = findDeepParent(transition.getTarget()); @@ -881,7 +882,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } } - notifyStateEntered(state, message, buildStateContext(message, null, getRelayStateMachine(), null, state)); + notifyStateEntered(state, message, buildStateContext(Stage.STATE_ENTRY, message, null, getRelayStateMachine(), null, state)); log.debug("Enter state=[" + state + "]"); state.entry(stateContext); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java index de317d41..21af2cea 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java @@ -33,6 +33,7 @@ import org.springframework.statemachine.transition.Transition; */ public class DefaultStateContext implements StateContext { + private final Stage stage; private final Message message; private final MessageHeaders messageHeaders; private final ExtendedState extendedState; @@ -42,13 +43,22 @@ public class DefaultStateContext implements StateContext { private final State target; private final Exception exception; - public DefaultStateContext(Message message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition transition, - StateMachine stateMachine) { - this(message, messageHeaders, extendedState, transition, stateMachine, null, null, null); - } - - public DefaultStateContext(Message message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition transition, - StateMachine stateMachine, State source, State target, Exception exception) { + /** + * Instantiates a new default state context. + * + * @param stage the stage + * @param message the message + * @param messageHeaders the message headers + * @param extendedState the extended state + * @param transition the transition + * @param stateMachine the state machine + * @param source the source + * @param target the target + * @param exception the exception + */ + public DefaultStateContext(Stage stage, Message message, MessageHeaders messageHeaders, ExtendedState extendedState, + Transition transition, StateMachine stateMachine, State source, State target, Exception exception) { + this.stage = stage; this.message = message; this.messageHeaders = messageHeaders; this.extendedState = extendedState; @@ -59,6 +69,11 @@ public class DefaultStateContext implements StateContext { this.exception = exception; } + @Override + public Stage getStage() { + return stage; + } + @Override public E getEvent() { return message != null ? message.getPayload() : null; @@ -113,4 +128,12 @@ public class DefaultStateContext implements StateContext { public Exception getException() { return exception; } + + @Override + public String toString() { + return "DefaultStateContext [stage=" + stage + ", message=" + message + ", messageHeaders=" + messageHeaders + ", extendedState=" + + extendedState + ", transition=" + transition + ", stateMachine=" + stateMachine + ", source=" + source + ", target=" + + target + ", exception=" + exception + "]"; + } + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java index c654bfd3..ec87785d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java @@ -38,6 +38,7 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.StateContext.Stage; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; import org.springframework.statemachine.trigger.DefaultTriggerContext; @@ -405,7 +406,7 @@ public class DefaultStateMachineExecutor extends LifecycleObjectSupport im // we want to keep the originating sm id map.put(StateMachineSystemConstants.STATEMACHINE_IDENTIFIER, stateMachine.getId()); } - return new DefaultStateContext(message, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine); + return new DefaultStateContext(Stage.TRANSITION, message, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine, null, null, null); } private void registerTriggerListener() { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java index 56b6e0b6..a391f3de 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineObjectSupport.java @@ -131,6 +131,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateChanged(State source, State target, Message message, StateContext stateContext) { stateMachineHandlerCallHelper.callOnStateChanged(getBeanName(), null, message, stateContext); stateListener.stateChanged(source, target); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -142,6 +143,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateEntered(State state, Message message, StateContext stateContext) { stateMachineHandlerCallHelper.callOnStateEntry(getBeanName(), null, message, stateContext); stateListener.stateEntered(state); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -153,6 +155,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateExited(State state, Message message, StateContext stateContext) { stateMachineHandlerCallHelper.callOnStateExit(getBeanName(), null, message, stateContext); stateListener.stateExited(state); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -164,6 +167,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyEventNotAccepted(Message event, StateContext stateContext) { stateMachineHandlerCallHelper.callOnEventNotAccepted(getBeanName(), stateContext); stateListener.eventNotAccepted(event); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -175,6 +179,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyTransitionStart(Transition transition, Message message, StateContext stateContext) { stateMachineHandlerCallHelper.callOnTransitionStart(getBeanName(), transition, message, stateContext); stateListener.transitionStarted(transition); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -186,6 +191,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyTransition(Transition transition, Message message, StateContext stateContext) { stateMachineHandlerCallHelper.callOnTransition(getBeanName(), transition, message, stateContext); stateListener.transition(transition); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -197,6 +203,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyTransitionEnd(Transition transition, Message message, StateContext stateContext) { stateMachineHandlerCallHelper.callOnTransitionEnd(getBeanName(), transition, message, stateContext); stateListener.transitionEnded(transition); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -208,6 +215,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateMachineStarted(StateMachine stateMachine, StateContext stateContext) { stateMachineHandlerCallHelper.callOnStateMachineStart(getBeanName(), stateContext); stateListener.stateMachineStarted(stateMachine); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -219,6 +227,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateMachineStopped(StateMachine stateMachine, StateContext stateContext) { stateMachineHandlerCallHelper.callOnStateMachineStop(getBeanName(), stateContext); stateListener.stateMachineStopped(stateMachine); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -230,6 +239,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyStateMachineError(StateMachine stateMachine, Exception exception, StateContext stateContext) { stateMachineHandlerCallHelper.callOnStateMachineError(getBeanName(), stateContext); stateListener.stateMachineError(stateMachine, exception); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -241,6 +251,7 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup protected void notifyExtendedStateChanged(Object key, Object value, StateContext stateContext) { stateMachineHandlerCallHelper.callOnExtendedStateChanged(getBeanName(), key, value, stateContext); stateListener.extendedStateChanged(key, value); + stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { @@ -331,6 +342,11 @@ public abstract class StateMachineObjectSupport extends LifecycleObjectSup stateListener.extendedStateChanged(key, value); } + @Override + public void stateContext(StateContext stateContext) { + stateListener.stateContext(stateContext); + } + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateContextTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateContextTests.java new file mode 100644 index 00000000..57fffacd --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateContextTests.java @@ -0,0 +1,230 @@ +/* + * 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.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Map; + +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; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; + +public class StateContextTests extends AbstractStateMachineTests { + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @Test + public void testStartCycles() throws Exception { + context.register(Config1.class); + context.refresh(); + @SuppressWarnings("unchecked") + StateMachine machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + + TestStateMachineListener listener = new TestStateMachineListener(); + machine.addStateListener(listener); + + machine.start(); + assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0, States.S1, States.S11)); + assertThat(listener.contexts.size(), is(19)); + // TODO: continue with other tests to verify context fields + } + + static class TestStateMachineListener extends StateMachineListenerAdapter { + + ArrayList> contexts = new ArrayList<>(); + + @Override + public void stateContext(StateContext stateContext) { + contexts.add(stateContext); + } + } + + @Configuration + @EnableStateMachine + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) + throws Exception { + states + .withStates() + .initial(States.S0, fooAction()) + .state(States.S0) + .and() + .withStates() + .parent(States.S0) + .initial(States.S1) + .state(States.S1) + .and() + .withStates() + .parent(States.S1) + .initial(States.S11) + .state(States.S11) + .state(States.S12) + .and() + .withStates() + .parent(States.S0) + .state(States.S2) + .and() + .withStates() + .parent(States.S2) + .initial(States.S21) + .state(States.S21) + .and() + .withStates() + .parent(States.S21) + .initial(States.S211) + .state(States.S211) + .state(States.S212); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) + throws Exception { + transitions + .withExternal() + .source(States.S1).target(States.S1).event(Events.A) + .guard(foo1Guard()) + .and() + .withExternal() + .source(States.S1).target(States.S11).event(Events.B) + .and() + .withExternal() + .source(States.S21).target(States.S211).event(Events.B) + .and() + .withExternal() + .source(States.S1).target(States.S2).event(Events.C) + .and() + .withExternal() + .source(States.S2).target(States.S1).event(Events.C) + .and() + .withExternal() + .source(States.S1).target(States.S0).event(Events.D) + .and() + .withExternal() + .source(States.S211).target(States.S21).event(Events.D) + .and() + .withExternal() + .source(States.S0).target(States.S211).event(Events.E) + .and() + .withExternal() + .source(States.S1).target(States.S211).event(Events.F) + .and() + .withExternal() + .source(States.S2).target(States.S11).event(Events.F) + .and() + .withExternal() + .source(States.S11).target(States.S211).event(Events.G) + .and() + .withExternal() + .source(States.S211).target(States.S0).event(Events.G) + .and() + .withInternal() + .source(States.S0).event(Events.H) + .guard(foo0Guard()) + .action(fooAction()) + .and() + .withInternal() + .source(States.S2).event(Events.H) + .guard(foo1Guard()) + .action(fooAction()) + .and() + .withInternal() + .source(States.S1).event(Events.H) + .and() + .withExternal() + .source(States.S11).target(States.S12).event(Events.I) + .and() + .withExternal() + .source(States.S211).target(States.S212).event(Events.I) + .and() + .withExternal() + .source(States.S12).target(States.S212).event(Events.I); + + } + + @Bean + public FooGuard foo0Guard() { + return new FooGuard(0); + } + + @Bean + public FooGuard foo1Guard() { + return new FooGuard(1); + } + + @Bean + public FooAction fooAction() { + return new FooAction(); + } + + } + + public static enum States { + S0, S1, S11, S12, S2, S21, S211, S212 + } + + public static enum Events { + A, B, C, D, E, F, G, H, I + } + + private static class FooAction implements Action { + + @Override + public void execute(StateContext context) { + Map variables = context.getExtendedState().getVariables(); + Integer foo = context.getExtendedState().get("foo", Integer.class); + if (foo == null) { + variables.put("foo", 0); + } else if (foo == 0) { + variables.put("foo", 1); + } else if (foo == 1) { + variables.put("foo", 0); + } + } + } + + private static class FooGuard implements Guard { + + private final int match; + + public FooGuard(int match) { + this.match = match; + } + + @Override + public boolean evaluate(StateContext context) { + Object foo = context.getExtendedState().getVariables().get("foo"); + return !(foo == null || !foo.equals(match)); + } + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java index ee50c2be..3b0bf69c 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java @@ -59,8 +59,8 @@ public class SpelExpressionGuardTests extends AbstractStateMachineTests { Map map = new HashMap(); map.put("foo", "bar"); MessageHeaders headers = new MessageHeaders(map); - DefaultStateContext stateContext = new DefaultStateContext(null, headers, null, null, null); - + DefaultStateContext stateContext = new DefaultStateContext(null, null, headers, + null, null, null, null, null, null); assertThat(guard.evaluate(stateContext), is(true)); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java index e9247d0f..e13224e3 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java @@ -219,6 +219,10 @@ public class ListenerTests extends AbstractStateMachineTests { extendedLatch.countDown(); } + @Override + public void stateContext(StateContext stateContext) { + } + } @Configuration diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/MethodParameterTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/MethodParameterTests.java index d8d62651..074820e3 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/MethodParameterTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/MethodParameterTests.java @@ -29,6 +29,7 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.ExtendedState; import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateContext.Stage; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.annotation.EventHeaders; import org.springframework.statemachine.annotation.OnTransition; @@ -62,7 +63,7 @@ public class MethodParameterTests { StateMachineRuntime runtime = new StateMachineRuntime() { @Override public StateContext getStateContext() { - return new DefaultStateContext(message, messageHeaders, extendedState, transition, stateMachine, source, + return new DefaultStateContext(Stage.TRANSITION, message, messageHeaders, extendedState, transition, stateMachine, source, target, exception); } }; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java index 7da0a286..f0f1b0ae 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/support/StateContextExpressionMethodsTests.java @@ -91,7 +91,7 @@ public class StateContextExpressionMethodsTests { extendedState.getVariables().put("boolean1", true); extendedState.getVariables().put("boolean2", false); StateContext stateContext = new DefaultStateContext( - MessageBuilder.withPayload(SpelEvents.E1).build(), messageHeaders, extendedState, new MockTransition(), stateMachine); + null, MessageBuilder.withPayload(SpelEvents.E1).build(), messageHeaders, extendedState, new MockTransition(), stateMachine, null, null, null); return stateContext; }