Fix some StateContext use cases
- This is part 1 of changes for #150. - Modify AbstractStateMachine to pass context in part of a stages. - Add some tests - Add preliminary docs
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
: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
|
||||
:sm-statecontext-stage: http://docs.spring.io/spring-statemachine/docs/{spring-statemachine-version}/api/org/springframework/statemachine/StateContext.Stage.html
|
||||
|
||||
= Spring Statemachine - Reference Documentation
|
||||
|
||||
|
||||
@@ -633,11 +633,46 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippet6]
|
||||
|
||||
[[sm-statecontext]]
|
||||
== Using StateContext
|
||||
{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
|
||||
case there is a need to send events to a further processing.
|
||||
{sm-statecontext}[_StateContext_] is a one of a most important objects
|
||||
when working with a state machine as it is passed into various methods
|
||||
and callbacks to give status of a current state of a state machine and
|
||||
where it is possibly going. If simplifying things a little it can be
|
||||
considered to be a snapshot of a current state machine stage where it
|
||||
is at a time _StateContext_ is passed on.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
In `Spring Statemachine 1.0.x` _StateContext_ usage were relatively naive
|
||||
in terms of how it was used to just pass stuff around as a simple `POJO`.
|
||||
Starting from `Spring Statemachine 1.1.x` its role has been greatly
|
||||
improved by making it a first class citizen in a state machine.
|
||||
====
|
||||
|
||||
In overall _StateContext_ can be used as.
|
||||
|
||||
* Access to current `Message`, `Event` or their
|
||||
`MessageHeaders` if known.
|
||||
* Access to state machine `Extended State`.
|
||||
* Access to `StateMachine` itself.
|
||||
* Access to possible state machine error.
|
||||
* Access to current `Transition` if applicable.
|
||||
* Access to _source_ and _target_ states where state machine is
|
||||
possibly getting from and going to.
|
||||
* Access to current `Stage` as described in <<sm-statecontext-stage>>.
|
||||
|
||||
_StateContext_ is passed into various components interacting with user
|
||||
like `Action` and `Guard`.
|
||||
|
||||
[[sm-statecontext-stage]]
|
||||
=== Stages
|
||||
{sm-statecontext-stage}[_Stage_] is representation of a `stage` on
|
||||
which a state machine is currently interacting with a user. Current
|
||||
stages are `EVENT_NOT_ACCEPTED`, `EXTENDED_STATE_CHANGED`,
|
||||
`STATE_CHANGED`, `STATE_ENTRY`, `STATE_EXIT`, `STATEMACHINE_ERROR`,
|
||||
`STATEMACHINE_START`, `STATEMACHINE_STOP`, `TRANSITION`,
|
||||
`TRANSITION_START` and `TRANSITION_END` which look very familiar as
|
||||
those match how user can interact with listeners as described in
|
||||
<<sm-listeners>>.
|
||||
|
||||
[[sm-triggers]]
|
||||
== Triggering Transitions
|
||||
@@ -737,6 +772,10 @@ In above example we simply created our own listener class
|
||||
_StateMachineEventListener_ which extends
|
||||
_StateMachineListenerAdapter_.
|
||||
|
||||
Listener method `stateContext` gives an access to various
|
||||
_StateContext_ changes on a different stages. More about about it in
|
||||
section <<sm-statecontext>>.
|
||||
|
||||
Once you have your own listener defined, it can be registered into a
|
||||
state machine via its interface as shown below. It's just a matter of
|
||||
flavour if it's hooked up within a spring configuration or done
|
||||
|
||||
@@ -261,18 +261,18 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
executor.setStateMachineExecutorTransit(new StateMachineExecutorTransit<S, E>() {
|
||||
|
||||
@Override
|
||||
public void transit(Transition<S, E> t, StateContext<S, E> stateContext, Message<E> queuedMessage) {
|
||||
public void transit(Transition<S, E> t, StateContext<S, E> ctx, Message<E> message) {
|
||||
// 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()));
|
||||
notifyTransitionStart(t, message, buildStateContext(Stage.TRANSITION_START, message, t, getRelayStateMachine()));
|
||||
notifyTransition(t, message, buildStateContext(Stage.TRANSITION, message, t, getRelayStateMachine()));
|
||||
if (t.getKind() == TransitionKind.INITIAL) {
|
||||
switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine());
|
||||
notifyStateMachineStarted(getRelayStateMachine(), buildStateContext(Stage.STATEMACHINE_START, queuedMessage, null, getRelayStateMachine()));
|
||||
switchToState(t.getTarget(), message, t, getRelayStateMachine());
|
||||
notifyStateMachineStarted(getRelayStateMachine(), buildStateContext(Stage.STATEMACHINE_START, message, t, getRelayStateMachine()));
|
||||
} else if (t.getKind() != TransitionKind.INTERNAL) {
|
||||
switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine());
|
||||
switchToState(t.getTarget(), message, t, getRelayStateMachine());
|
||||
}
|
||||
// TODO: looks like events should be called here and anno processing earlier
|
||||
notifyTransitionEnd(t, queuedMessage, buildStateContext(Stage.TRANSITION_END, queuedMessage, null, getRelayStateMachine()));
|
||||
notifyTransitionEnd(t, message, buildStateContext(Stage.TRANSITION_END, message, t, getRelayStateMachine()));
|
||||
}
|
||||
});
|
||||
stateMachineExecutor = executor;
|
||||
@@ -889,7 +889,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
}
|
||||
|
||||
notifyStateEntered(state, message, buildStateContext(Stage.STATE_ENTRY, message, null, getRelayStateMachine(), null, state));
|
||||
notifyStateEntered(state, message, buildStateContext(Stage.STATE_ENTRY, message, transition, getRelayStateMachine(), null, state));
|
||||
log.debug("Enter state=[" + state + "]");
|
||||
state.entry(stateContext);
|
||||
}
|
||||
|
||||
@@ -116,12 +116,12 @@ public class DefaultStateContext<S, E> implements StateContext<S, E> {
|
||||
|
||||
@Override
|
||||
public State<S, E> getSource() {
|
||||
return source;
|
||||
return source != null ? source : (transition != null ? transition.getSource() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> getTarget() {
|
||||
return target;
|
||||
return target != null ? target : (transition != null ? transition.getTarget() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,17 +15,25 @@
|
||||
*/
|
||||
package org.springframework.statemachine;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.FeatureMatcher;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.StateContext.Stage;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
@@ -41,11 +49,11 @@ public class StateContextTests extends AbstractStateMachineTests {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@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();
|
||||
@@ -53,8 +61,87 @@ public class StateContextTests extends AbstractStateMachineTests {
|
||||
|
||||
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
|
||||
assertThat(listener.contexts, hasSize(19));
|
||||
|
||||
assertThat(listener.contexts, contains(
|
||||
hasStage(Stage.EXTENDED_STATE_CHANGED),
|
||||
hasStage(Stage.TRANSITION_START),
|
||||
hasStage(Stage.TRANSITION),
|
||||
hasStage(Stage.STATE_ENTRY),
|
||||
hasStage(Stage.TRANSITION_START),
|
||||
hasStage(Stage.TRANSITION),
|
||||
hasStage(Stage.STATE_ENTRY),
|
||||
hasStage(Stage.TRANSITION_START),
|
||||
hasStage(Stage.TRANSITION),
|
||||
hasStage(Stage.STATE_ENTRY),
|
||||
hasStage(Stage.STATE_CHANGED),
|
||||
hasStage(Stage.STATEMACHINE_START),
|
||||
hasStage(Stage.TRANSITION_END),
|
||||
hasStage(Stage.STATE_CHANGED),
|
||||
hasStage(Stage.STATEMACHINE_START),
|
||||
hasStage(Stage.TRANSITION_END),
|
||||
hasStage(Stage.STATE_CHANGED),
|
||||
hasStage(Stage.STATEMACHINE_START),
|
||||
hasStage(Stage.TRANSITION_END)
|
||||
));
|
||||
|
||||
assertThat(listener.contexts.get(0).getStage(), is(Stage.EXTENDED_STATE_CHANGED));
|
||||
|
||||
assertThat(listener.contexts.get(1).getStage(), is(Stage.TRANSITION_START));
|
||||
|
||||
assertThat(listener.contexts.get(2).getStage(), is(Stage.TRANSITION));
|
||||
assertThat(listener.contexts.get(2).getTransition(), notNullValue());
|
||||
assertThat(listener.contexts.get(2).getTransition().getSource(), nullValue());
|
||||
assertThat(listener.contexts.get(2).getTransition().getTarget(), notNullValue());
|
||||
assertThat(listener.contexts.get(2).getTransition().getTarget().getId(), is(States.S0));
|
||||
assertThat(listener.contexts.get(2).getSource(), nullValue());
|
||||
assertThat(listener.contexts.get(2).getTarget(), notNullValue());
|
||||
|
||||
|
||||
assertThat(listener.contexts.get(3).getStage(), is(Stage.STATE_ENTRY));
|
||||
assertThat(listener.contexts.get(3).getTarget(), notNullValue());
|
||||
assertThat(listener.contexts.get(3).getTarget().getId(), is(States.S0));
|
||||
assertThat(listener.contexts.get(3).getTransition(), notNullValue());
|
||||
|
||||
assertThat(listener.contexts.get(4).getStage(), is(Stage.TRANSITION_START));
|
||||
|
||||
assertThat(listener.contexts.get(5).getStage(), is(Stage.TRANSITION));
|
||||
|
||||
assertThat(listener.contexts.get(6).getStage(), is(Stage.STATE_ENTRY));
|
||||
assertThat(listener.contexts.get(6).getTarget(), notNullValue());
|
||||
assertThat(listener.contexts.get(6).getTarget().getId(), is(States.S1));
|
||||
// assertThat(listener.contexts.get(6).getTransition(), notNullValue());
|
||||
|
||||
assertThat(listener.contexts.get(7).getStage(), is(Stage.TRANSITION_START));
|
||||
|
||||
assertThat(listener.contexts.get(8).getStage(), is(Stage.TRANSITION));
|
||||
|
||||
assertThat(listener.contexts.get(9).getStage(), is(Stage.STATE_ENTRY));
|
||||
assertThat(listener.contexts.get(9).getTarget(), notNullValue());
|
||||
assertThat(listener.contexts.get(9).getTarget().getId(), is(States.S11));
|
||||
// assertThat(listener.contexts.get(9).getTransition(), notNullValue());
|
||||
|
||||
assertThat(listener.contexts.get(10).getStage(), is(Stage.STATE_CHANGED));
|
||||
|
||||
assertThat(listener.contexts.get(11).getStage(), is(Stage.STATEMACHINE_START));
|
||||
assertThat(listener.contexts.get(11).getTransition(), notNullValue());
|
||||
|
||||
assertThat(listener.contexts.get(12).getStage(), is(Stage.TRANSITION_END));
|
||||
|
||||
assertThat(listener.contexts.get(13).getStage(), is(Stage.STATE_CHANGED));
|
||||
|
||||
assertThat(listener.contexts.get(14).getStage(), is(Stage.STATEMACHINE_START));
|
||||
assertThat(listener.contexts.get(14).getTransition(), notNullValue());
|
||||
|
||||
assertThat(listener.contexts.get(15).getStage(), is(Stage.TRANSITION_END));
|
||||
|
||||
assertThat(listener.contexts.get(16).getStage(), is(Stage.STATE_CHANGED));
|
||||
|
||||
assertThat(listener.contexts.get(17).getStage(), is(Stage.STATEMACHINE_START));
|
||||
assertThat(listener.contexts.get(17).getTransition(), notNullValue());
|
||||
|
||||
assertThat(listener.contexts.get(18).getStage(), is(Stage.TRANSITION_END));
|
||||
// assertThat(listener.contexts.get(18).getTransition(), notNullValue());
|
||||
}
|
||||
|
||||
static class TestStateMachineListener extends StateMachineListenerAdapter<States, Events> {
|
||||
@@ -67,6 +154,15 @@ public class StateContextTests extends AbstractStateMachineTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<StateContext<?, ?>> hasStage(final Stage stage) {
|
||||
return new FeatureMatcher<StateContext<?, ?>, Stage>(equalTo(stage), "stage", "stage") {
|
||||
@Override
|
||||
protected Stage featureValueOf(final StateContext<?, ?> actual) {
|
||||
return actual.getStage();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<States, Events> {
|
||||
|
||||
@@ -396,6 +396,10 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
||||
@Override
|
||||
public void stateMachineError(StateMachine<States, Events> stateMachine, Exception exception) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateContext(StateContext<States, Events> stateContext) {
|
||||
}
|
||||
}
|
||||
// end::snippetH[]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user