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:
Janne Valkealahti
2015-12-31 17:39:57 +00:00
parent 79c49c5a78
commit 4aa18a4ca5
6 changed files with 159 additions and 19 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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