Add StateContext into StateMachineListener

- new StateMachineListener method for listening all
  StateContext changes in a various stages.
- StateContext now has a stage enum field telling at
  which stage it is.
- Polish some classes and docs.
- Prepare tests for StateContext.
- Relates to #126, #138 and #150
This commit is contained in:
Janne Valkealahti
2015-12-26 16:37:56 +00:00
parent 71fb0ce869
commit 4f5eb88626
18 changed files with 369 additions and 45 deletions

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -37,6 +37,13 @@ import org.springframework.statemachine.transition.Transition;
*/
public interface StateContext<S, E> {
/**
* 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<S, E> {
*/
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;
}
}

View File

@@ -38,7 +38,7 @@ public abstract class StateMachineEvent extends ApplicationEvent {
@Override
public String toString() {
return "AbstractStateMachineEvent [source=" + source + "]";
return "StateMachineEvent [source=" + source + "]";
}
}

View File

@@ -50,7 +50,7 @@ public abstract class TransitionEvent extends StateMachineEvent {
@Override
public String toString() {
return "OnTransitionStartEvent [transition=" + transition + "]";
return "TransitionEvent [transition=" + transition + "]";
}
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public class CompositeStateMachineListener<S,E> extends AbstractCompositeListener<StateMachineListener<S,E>> implements
StateMachineListener<S, E> {
public class CompositeStateMachineListener<S, E> extends AbstractCompositeListener<StateMachineListener<S, E>>
implements StateMachineListener<S, E> {
@Override
public void stateChanged(State<S, E> from, State<S, E> to) {
@@ -121,4 +122,12 @@ public class CompositeStateMachineListener<S,E> extends AbstractCompositeListene
}
}
@Override
public void stateContext(StateContext<S, E> stateContext) {
for (Iterator<StateMachineListener<S, E>> iterator = getListeners().reverse(); iterator.hasNext();) {
StateMachineListener<S, E> listener = iterator.next();
listener.stateContext(stateContext);
}
}
}

View File

@@ -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<S,E> {
*/
void extendedStateChanged(Object key, Object value);
/**
* Notified on various {@link Stage}s about a {@link StateContext}.
*
* @param stateContext the state context
*/
void stateContext(StateContext<S, E> stateContext);
}

View File

@@ -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<S, E> implements StateMachineListener<S
public void extendedStateChanged(Object key, Object value) {
}
@Override
public void stateContext(StateContext<S, E> stateContext) {
}
}

View File

@@ -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<S, E> extends StateMachineObjectSuppo
public boolean sendEvent(Message<E> 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<S, E> 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<S, E> 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<S, E> extends StateMachineObjectSuppo
@Override
public void transit(Transition<S, E> t, StateContext<S, E> stateContext, Message<E> queuedMessage) {
StateContext<S, E> 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<S, E> extends StateMachineObjectSuppo
// assume that state was set/reseted so we need to
// dispatch started event which would net getting
// dispatched via executor
StateContext<S, E> stateContext = buildStateContext(null, null, getRelayStateMachine());
StateContext<S, E> stateContext = buildStateContext(Stage.STATEMACHINE_START, null, null, getRelayStateMachine());
notifyStateMachineStarted(getRelayStateMachine(), stateContext);
return;
}
@@ -325,7 +326,7 @@ public abstract class AbstractStateMachine<S, E> 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<S, E> 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<S, E> extends StateMachineObjectSuppo
PseudoStateKind kind = state.getPseudoState() != null ? state.getPseudoState().getKind() : null;
if (kind == PseudoStateKind.CHOICE || kind == PseudoStateKind.HISTORY_SHALLOW
|| kind == PseudoStateKind.HISTORY_DEEP) {
StateContext<S, E> stateContext = buildStateContext(message, transition, stateMachine);
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine);
State<S, E> toState = state.getPseudoState().entry(stateContext);
if (kind == PseudoStateKind.CHOICE) {
@@ -659,7 +660,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
public void onContext(PseudoStateContext<S, E> context) {
PseudoState<S, E> pseudoState = context.getPseudoState();
State<S, E> toState = findStateWithPseudoState(pseudoState);
StateContext<S, E> stateContext = buildStateContext(null, null, getRelayStateMachine());
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_EXIT, null, null, getRelayStateMachine());
pseudoState.exit(stateContext);
switchToState(toState, null, null, getRelayStateMachine());
}
@@ -677,22 +678,22 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
return null;
}
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
private StateContext<S, E> buildStateContext(Stage stage, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(message, messageHeaders, extendedState, transition, stateMachine);
return new DefaultStateContext<S, E>(stage, message, messageHeaders, extendedState, transition, stateMachine, null, null, null);
}
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, Exception exception) {
private StateContext<S, E> buildStateContext(Stage stage, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, Exception exception) {
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(message, messageHeaders, extendedState, transition, stateMachine, null, null, exception);
return new DefaultStateContext<S, E>(stage, message, messageHeaders, extendedState, transition, stateMachine, null, null, exception);
}
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target) {
private StateContext<S, E> buildStateContext(Stage stage, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target) {
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(message, messageHeaders, extendedState, transition, stateMachine, source, target, null);
return new DefaultStateContext<S, E>(stage, message, messageHeaders, extendedState, transition, stateMachine, source, target, null);
}
private State<S, E> findDeepParent(State<S, E> state) {
@@ -727,7 +728,7 @@ public abstract class AbstractStateMachine<S, E> 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<S, E> 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<S, E> extends StateMachineObjectSuppo
return;
}
log.trace("Trying Exit state=[" + state + "]");
StateContext<S, E> stateContext = buildStateContext(message, transition, stateMachine);
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_EXIT, message, transition, stateMachine);
if (transition != null) {
@@ -853,7 +854,7 @@ public abstract class AbstractStateMachine<S, E> 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<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
@@ -861,7 +862,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
return;
}
log.trace("Trying Enter state=[" + state + "]");
StateContext<S, E> stateContext = buildStateContext(message, transition, stateMachine);
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_ENTRY, message, transition, stateMachine);
if (transition != null) {
State<S, E> findDeep1 = findDeepParent(transition.getTarget());
@@ -881,7 +882,7 @@ public abstract class AbstractStateMachine<S, E> 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);
}

View File

@@ -33,6 +33,7 @@ import org.springframework.statemachine.transition.Transition;
*/
public class DefaultStateContext<S, E> implements StateContext<S, E> {
private final Stage stage;
private final Message<E> message;
private final MessageHeaders messageHeaders;
private final ExtendedState extendedState;
@@ -42,13 +43,22 @@ public class DefaultStateContext<S, E> implements StateContext<S, E> {
private final State<S, E> target;
private final Exception exception;
public DefaultStateContext(Message<E> message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S, E> transition,
StateMachine<S, E> stateMachine) {
this(message, messageHeaders, extendedState, transition, stateMachine, null, null, null);
}
public DefaultStateContext(Message<E> message, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S, E> transition,
StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> 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<E> message, MessageHeaders messageHeaders, ExtendedState extendedState,
Transition<S, E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target, Exception exception) {
this.stage = stage;
this.message = message;
this.messageHeaders = messageHeaders;
this.extendedState = extendedState;
@@ -59,6 +69,11 @@ public class DefaultStateContext<S, E> implements StateContext<S, E> {
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<S, E> implements StateContext<S, E> {
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 + "]";
}
}

View File

@@ -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<S, E> extends LifecycleObjectSupport im
// we want to keep the originating sm id
map.put(StateMachineSystemConstants.STATEMACHINE_IDENTIFIER, stateMachine.getId());
}
return new DefaultStateContext<S, E>(message, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine);
return new DefaultStateContext<S, E>(Stage.TRANSITION, message, new MessageHeaders(map), stateMachine.getExtendedState(), transition, stateMachine, null, null, null);
}
private void registerTriggerListener() {

View File

@@ -131,6 +131,7 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
protected void notifyStateChanged(State<S,E> source, State<S,E> target, Message<E> message, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyStateEntered(State<S,E> state, Message<E> message, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyStateExited(State<S,E> state, Message<E> message, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyEventNotAccepted(Message<E> event, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyTransitionStart(Transition<S,E> transition, Message<E> message, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyTransition(Transition<S,E> transition, Message<E> message, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyTransitionEnd(Transition<S,E> transition, Message<E> message, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyStateMachineStarted(StateMachine<S, E> stateMachine, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyStateMachineStopped(StateMachine<S, E> stateMachine, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyStateMachineError(StateMachine<S, E> stateMachine, Exception exception, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
protected void notifyExtendedStateChanged(Object key, Object value, StateContext<S, E> 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<S, E> extends LifecycleObjectSup
stateListener.extendedStateChanged(key, value);
}
@Override
public void stateContext(StateContext<S, E> stateContext) {
stateListener.stateContext(stateContext);
}
}
}

View File

@@ -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<States, Events> 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<States, Events> {
ArrayList<StateContext<States, Events>> contexts = new ArrayList<>();
@Override
public void stateContext(StateContext<States, Events> stateContext) {
contexts.add(stateContext);
}
}
@Configuration
@EnableStateMachine
static class Config1 extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> 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<States, Events> 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<States, Events> {
@Override
public void execute(StateContext<States, Events> context) {
Map<Object, Object> 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<States, Events> {
private final int match;
public FooGuard(int match) {
this.match = match;
}
@Override
public boolean evaluate(StateContext<States, Events> context) {
Object foo = context.getExtendedState().getVariables().get("foo");
return !(foo == null || !foo.equals(match));
}
}
}

View File

@@ -59,8 +59,8 @@ public class SpelExpressionGuardTests extends AbstractStateMachineTests {
Map<String, Object> map = new HashMap<String, Object>();
map.put("foo", "bar");
MessageHeaders headers = new MessageHeaders(map);
DefaultStateContext<TestStates, TestEvents> stateContext = new DefaultStateContext<TestStates, TestEvents>(null, headers, null, null, null);
DefaultStateContext<TestStates, TestEvents> stateContext = new DefaultStateContext<TestStates, TestEvents>(null, null, headers,
null, null, null, null, null, null);
assertThat(guard.evaluate(stateContext), is(true));
}

View File

@@ -219,6 +219,10 @@ public class ListenerTests extends AbstractStateMachineTests {
extendedLatch.countDown();
}
@Override
public void stateContext(StateContext<TestStates, TestEvents> stateContext) {
}
}
@Configuration

View File

@@ -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<String, String> runtime = new StateMachineRuntime<String, String>() {
@Override
public StateContext<String, String> getStateContext() {
return new DefaultStateContext<String, String>(message, messageHeaders, extendedState, transition, stateMachine, source,
return new DefaultStateContext<String, String>(Stage.TRANSITION, message, messageHeaders, extendedState, transition, stateMachine, source,
target, exception);
}
};

View File

@@ -91,7 +91,7 @@ public class StateContextExpressionMethodsTests {
extendedState.getVariables().put("boolean1", true);
extendedState.getVariables().put("boolean2", false);
StateContext<SpelStates, SpelEvents> stateContext = new DefaultStateContext<SpelStates, SpelEvents>(
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;
}