Event defer for substates and regions
- Modify state structure so that nested states and orthogonal regions can work with deferred events. - Change executor to work better with deferred events and try to provide better model for run-to-completion when execution happens in a threads. - Add new event defer section in ref docs. - Fixes #131
This commit is contained in:
@@ -415,6 +415,64 @@ is attached automatically and then a default _TaskExecutor_ can be found
|
||||
from there. If instances are used outside of a spring application context
|
||||
these methods must be used to setup needed facilities.
|
||||
|
||||
[[sm-deferevents]]
|
||||
== Using Deferred Events
|
||||
When en event is sent it may fire an `EventTrigger` which then may cause
|
||||
a transition to happen if a state machine is in a state where trigger is
|
||||
evaluated successfully. Normally this may lead to a situation where
|
||||
an event is not accepted and is dropped. However it may be desirable to
|
||||
postpone this event until a state machine enters other state, in which
|
||||
it is possible to accept that event. In other words an event simply
|
||||
arrives at an inconvenient time.
|
||||
|
||||
Spring Statemachine provides a mechanism for deferring events for later
|
||||
processing. Every state can have a list of deferred events. If an event
|
||||
in the current state’s deferred event list occurs, the event will be saved
|
||||
(deferred) for future processing until a state is entered that does not list
|
||||
the event in its deferred event list. When such a state is entered, the
|
||||
state machine will automatically recall any saved events that are no longer
|
||||
deferred and will then either consume or discard these events. It is possible
|
||||
for a superstate to have a transition defined on an event that is deferred
|
||||
by a substate. Following same hierarchical state machines concepts, the substate
|
||||
takes precedence over the superstate, the event will be deferred and the
|
||||
transition for the superstate will not be executed. With orthogonal regions
|
||||
where one orthogonal region defers an event and another accepts the event, the
|
||||
accept takes precedence and the event is consumed and not deferred.
|
||||
|
||||
The most obvious use case for event deferring is when an event is causing
|
||||
a transition into a particular state and state machine is then returned back
|
||||
to its original state where second event should cause a same transition. Lets
|
||||
take this with a simple example.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests2.java[tags=snippetE]
|
||||
----
|
||||
|
||||
In above state machine has state _READY_ which indicates that machine is
|
||||
ready to process events which would take it into a _DEPLOY_ state where the
|
||||
actual deployment would happen. After deploy actions has been executed machine
|
||||
is then returned back into a _READY_ state. Sending multiple events in a
|
||||
_READY_ state is not causing any trouble if machine is using synchronous executor
|
||||
because event sending would block between event calls. However if executor is using
|
||||
threads then other events may get lost because machine is no longer in a state where
|
||||
event could be processed. Thus deferring some of these events allows machine to
|
||||
preserve these events.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::samples/DocsConfigurationSampleTests2.java[tags=snippetF]
|
||||
----
|
||||
|
||||
In above state machine which is using nested states instead of a flat
|
||||
state model, event _DEPLOY_ can be deferred directly in a substate.
|
||||
It is also showing concept of deferring event _DONE_ in one of a
|
||||
sub-states which would then override anonymous transition between
|
||||
_DEPLOY_ and _DONE_ states if state machine happens to be in a
|
||||
_DEPLOYPREPARE_ state when _DONE_ event is dispatched. In
|
||||
_DEPLOYEXECUTE_ state _DONE_ event is not deferred, thus event would
|
||||
be handled in a super state.
|
||||
|
||||
[[sm-scopes]]
|
||||
== Using Scopes
|
||||
Support for scopes in a state machine is very limited but it is possible
|
||||
|
||||
@@ -152,6 +152,11 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDefer(Message<E> event) {
|
||||
return deferred != null && deferred.contains(event.getPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void exit(StateContext<S, E> context);
|
||||
|
||||
|
||||
@@ -107,6 +107,25 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
return accept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDefer(Message<E> event) {
|
||||
boolean defer = true;
|
||||
if (getRegions() != null) {
|
||||
for (Region<S, E> r : getRegions()) {
|
||||
State<S, E> state = r.getState();
|
||||
if (state != null) {
|
||||
Collection<E> deferredEvents = state.getDeferredEvents();
|
||||
if (deferredEvents != null && deferredEvents.contains(event.getPayload())) {
|
||||
defer = defer & true;
|
||||
} else {
|
||||
defer = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return defer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(StateContext<S, E> context) {
|
||||
for (Region<S, E> region : getRegions()) {
|
||||
|
||||
@@ -39,6 +39,14 @@ public interface State<S, E> {
|
||||
*/
|
||||
boolean sendEvent(Message<E> event);
|
||||
|
||||
/**
|
||||
* Checks if state wants to defer an event.
|
||||
*
|
||||
* @param event the wrapped event
|
||||
* @return true if event should be deferred
|
||||
*/
|
||||
boolean shouldDefer(Message<E> event);
|
||||
|
||||
/**
|
||||
* Initiate an exit sequence for the state.
|
||||
*
|
||||
|
||||
@@ -239,6 +239,21 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
return super.sendEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDefer(Message<E> event) {
|
||||
StateMachine<S, E> machine = getSubmachine();
|
||||
if (machine != null) {
|
||||
State<S, E> state = machine.getState();
|
||||
if (state != null) {
|
||||
Collection<E> deferredEvents = state.getDeferredEvents();
|
||||
if (deferredEvents != null && deferredEvents.contains(event.getPayload())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.shouldDefer(event);
|
||||
}
|
||||
|
||||
private boolean isLocal(StateContext<S, E> context) {
|
||||
Transition<S, E> transition = context.getTransition();
|
||||
if (transition != null && TransitionKind.LOCAL == transition.getKind() && this == transition.getTarget()) {
|
||||
|
||||
@@ -571,35 +571,30 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
|
||||
protected boolean acceptEvent(Message<E> message) {
|
||||
boolean accepted = (currentState != null && currentState.sendEvent(message));
|
||||
if (accepted) {
|
||||
if ((currentState != null && currentState.shouldDefer(message))) {
|
||||
log.info("Current state " + currentState + " deferred event " + message);
|
||||
stateMachineExecutor.queueDeferredEvent(message);
|
||||
return true;
|
||||
}
|
||||
if ((currentState != null && currentState.sendEvent(message))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Queue event " + message);
|
||||
log.debug("Queue event " + message + " " + this);
|
||||
}
|
||||
|
||||
Message<E> defer = null;
|
||||
for (Transition<S,E> transition : transitions) {
|
||||
State<S,E> source = transition.getSource();
|
||||
Trigger<S, E> trigger = transition.getTrigger();
|
||||
|
||||
if (StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
|
||||
if (trigger != null && trigger.evaluate(new DefaultTriggerContext<S, E>(message.getPayload()))) {
|
||||
stateMachineExecutor.queueTrigger(trigger, message);
|
||||
stateMachineExecutor.queueEvent(message);
|
||||
return true;
|
||||
} else if (source.getDeferredEvents() != null && source.getDeferredEvents().contains(message.getPayload())) {
|
||||
defer = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defer != null) {
|
||||
log.info("Deferring event " + defer);
|
||||
stateMachineExecutor.queueDeferredEvent(defer);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
|
||||
@Override
|
||||
public void queueTrigger(Trigger<S, E> trigger, Message<E> message) {
|
||||
log.debug("Queue trigger " + trigger);
|
||||
triggerQueue.add(new TriggerQueueItem(trigger, message));
|
||||
}
|
||||
|
||||
@@ -227,10 +228,19 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
processEventQueue();
|
||||
processTriggerQueue();
|
||||
while (processDeferList()) {
|
||||
boolean eventProcessed = false;
|
||||
while (processEventQueue()) {
|
||||
eventProcessed = true;
|
||||
processTriggerQueue();
|
||||
while (processDeferList()) {
|
||||
processTriggerQueue();
|
||||
}
|
||||
}
|
||||
if (!eventProcessed) {
|
||||
processTriggerQueue();
|
||||
while (processDeferList()) {
|
||||
processTriggerQueue();
|
||||
}
|
||||
}
|
||||
taskRef.set(null);
|
||||
if (requestTask.getAndSet(false)) {
|
||||
@@ -246,32 +256,30 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
}
|
||||
}
|
||||
|
||||
private void processEventQueue() {
|
||||
private boolean processEventQueue() {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Process event queue, size=" + eventQueue.size());
|
||||
}
|
||||
Message<E> queuedEvent = null;
|
||||
Message<E> queuedEvent = eventQueue.poll();
|
||||
State<S,E> currentState = stateMachine.getState();
|
||||
while ((queuedEvent = eventQueue.poll()) != null) {
|
||||
Message<E> defer = null;
|
||||
for (Transition<S, E> transition : transitions) {
|
||||
State<S, E> source = transition.getSource();
|
||||
if (queuedEvent != null) {
|
||||
if ((currentState != null && currentState.shouldDefer(queuedEvent))) {
|
||||
queueDeferredEvent(queuedEvent);
|
||||
return true;
|
||||
}
|
||||
for (Transition<S,E> transition : transitions) {
|
||||
State<S,E> source = transition.getSource();
|
||||
Trigger<S, E> trigger = transition.getTrigger();
|
||||
|
||||
if (StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
|
||||
if (trigger != null && trigger.evaluate(new DefaultTriggerContext<S, E>(queuedEvent.getPayload()))) {
|
||||
triggerQueue.add(new TriggerQueueItem(trigger, queuedEvent));
|
||||
} else if (source.getDeferredEvents() != null
|
||||
&& source.getDeferredEvents().contains(queuedEvent.getPayload())) {
|
||||
defer = queuedEvent;
|
||||
queueTrigger(trigger, queuedEvent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defer != null) {
|
||||
log.info("Deferring event " + defer);
|
||||
deferList.addLast(defer);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processTriggerQueue() {
|
||||
@@ -290,19 +298,16 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
return;
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Process trigger queue, size=" + triggerQueue.size());
|
||||
log.debug("Process trigger queue, size=" + triggerQueue.size() + " " + this);
|
||||
}
|
||||
TriggerQueueItem queueItem = null;
|
||||
// keep last message here so that we can
|
||||
TriggerQueueItem queueItem = triggerQueue.poll();
|
||||
// keep message here so that we can
|
||||
// pass it to triggerless transitions
|
||||
while ((queueItem = triggerQueue.poll()) != null) {
|
||||
|
||||
State<S,E> currentState = stateMachine.getState();
|
||||
|
||||
if (currentState == null) {
|
||||
continue;
|
||||
State<S,E> currentState = stateMachine.getState();
|
||||
if (queueItem != null && currentState != null) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Process trigger item " + queueItem + " " + this);
|
||||
}
|
||||
|
||||
// queued message is kept on a class level order to let
|
||||
// triggerless transition to receive this message if it doesn't
|
||||
// kick in in this poll loop.
|
||||
@@ -351,15 +356,18 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processDeferList() {
|
||||
private synchronized boolean processDeferList() {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Process defer list, size=" + deferList.size());
|
||||
}
|
||||
boolean triggered = false;
|
||||
ListIterator<Message<E>> iterator = deferList.listIterator();
|
||||
State<S,E> currentState = stateMachine.getState();
|
||||
while (iterator.hasNext()) {
|
||||
Message<E> event = iterator.next();
|
||||
if (currentState.shouldDefer(event)) {
|
||||
// if current state still defers, just continue with others
|
||||
continue;
|
||||
}
|
||||
for (Transition<S, E> transition : transitions) {
|
||||
State<S, E> source = transition.getSource();
|
||||
Trigger<S, E> trigger = transition.getTrigger();
|
||||
@@ -367,12 +375,13 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
if (trigger != null && trigger.evaluate(new DefaultTriggerContext<S, E>(event.getPayload()))) {
|
||||
triggerQueue.add(new TriggerQueueItem(trigger, event));
|
||||
iterator.remove();
|
||||
triggered = true;
|
||||
// bail out when first deferred message is causing a trigger to fire
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return triggered;
|
||||
return false;
|
||||
}
|
||||
|
||||
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.core.task.TaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.State;
|
||||
|
||||
public class EventDeferTests extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeferWithFlat() throws Exception {
|
||||
context.register(Config2.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
machine.sendEvent("E3");
|
||||
machine.sendEvent("E1");
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(1));
|
||||
machine.sendEvent("E2");
|
||||
assertThat(readField.size(), is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeferWithFlatThreadExecutor() throws Exception {
|
||||
context.register(Config2.class, ExecutorConfig.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.stateChangedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
listener.reset(1, 0, 0, 0);
|
||||
machine.sendEvent("E3");
|
||||
assertThat(listener.stateChangedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
machine.sendEvent("E1");
|
||||
machine.sendEvent("E1");
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(2));
|
||||
machine.sendEvent("E2");
|
||||
assertThat(readField.size(), is(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeferWithSubsSyncExecutor() throws Exception {
|
||||
context.register(Config1.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.stateChangedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
listener.reset(0, 0, 0, 1);
|
||||
machine.sendEvent("E3");
|
||||
assertThat(listener.sub3readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.sub3readyStateEnteredCount, is(1));
|
||||
|
||||
machine.sendEvent("E1");
|
||||
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(1));
|
||||
|
||||
listener.reset(0, 0, 2, 0);
|
||||
machine.sendEvent("E4");
|
||||
assertThat(listener.readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(2));
|
||||
|
||||
assertThat(machine.getState().getIds(), contains("READY"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeferWithSubsThreadExecutor() throws Exception {
|
||||
context.register(Config1.class, ExecutorConfig.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.stateChangedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
listener.reset(0, 0, 0, 1);
|
||||
machine.sendEvent("E3");
|
||||
assertThat(listener.sub3readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.sub3readyStateEnteredCount, is(1));
|
||||
|
||||
listener.reset(0, 0, 2, 0);
|
||||
machine.sendEvent("E1");
|
||||
machine.sendEvent("E1");
|
||||
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(2));
|
||||
|
||||
listener.reset(0, 0, 3, 0);
|
||||
machine.sendEvent("E4");
|
||||
assertThat(listener.readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(3));
|
||||
|
||||
assertThat(machine.getState().getIds(), contains("READY"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubNotDeferOverrideSuperTransition() throws Exception {
|
||||
context.register(Config3.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), contains("SUB1", "SUB11"));
|
||||
|
||||
// sub doesn't defer
|
||||
machine.sendEvent("E15");
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(0));
|
||||
|
||||
assertThat(machine.getState().getIds(), contains("SUB5"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubDeferOverrideSuperTransition() throws Exception {
|
||||
context.register(Config3.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), contains("SUB1", "SUB11"));
|
||||
|
||||
machine.sendEvent("E1112");
|
||||
assertThat(machine.getState().getIds(), contains("SUB1", "SUB12"));
|
||||
|
||||
// sub defers
|
||||
machine.sendEvent("E15");
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(1));
|
||||
|
||||
assertThat(machine.getState().getIds(), contains("SUB1", "SUB12"));
|
||||
|
||||
// from SUB12 to SUB11 should then cause E15 to fire in root
|
||||
// causing SUB1 to SUB5
|
||||
machine.sendEvent("E1211");
|
||||
assertThat(machine.getState().getIds(), contains("SUB5"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegionOneDeferTransition() throws Exception {
|
||||
context.register(Config4.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("SUB111", "SUB1", "SUB121"));
|
||||
|
||||
machine.sendEvent("E5");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("SUB112", "SUB1", "SUB121"));
|
||||
|
||||
// regions defers
|
||||
machine.sendEvent("E3");
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegionAllDeferTransition() throws Exception {
|
||||
context.register(Config4.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("SUB111", "SUB1", "SUB121"));
|
||||
|
||||
machine.sendEvent("E5");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("SUB112", "SUB1", "SUB121"));
|
||||
|
||||
machine.sendEvent("E8");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("SUB112", "SUB1", "SUB122"));
|
||||
|
||||
// regions defers
|
||||
machine.sendEvent("E3");
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegionNotDeferTransition() throws Exception {
|
||||
context.register(Config4.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("SUB111", "SUB1", "SUB121"));
|
||||
|
||||
// regions doesn't defer
|
||||
machine.sendEvent("E3");
|
||||
Object executor = TestUtils.readField("stateMachineExecutor", machine);
|
||||
List<?> readField = TestUtils.readField("deferList", executor);
|
||||
assertThat(readField.size(), is(0));
|
||||
|
||||
assertThat(machine.getState().getIds(), contains("SUB2"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("READY")
|
||||
.state("SUB1")
|
||||
.state("SUB2", "E1", "E2")
|
||||
.state("SUB3", "E1", "E2")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB1")
|
||||
.initial("SUB1READY")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB2")
|
||||
.initial("SUB2READY")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB3")
|
||||
.initial("SUB3READY");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("READY").target("SUB1")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("SUB2")
|
||||
.event("E2")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("SUB3")
|
||||
.event("E3")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB1READY").target("READY")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB2READY").target("READY")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB3").target("READY")
|
||||
.event("NOTUSED")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB3READY").target("READY")
|
||||
.event("E4");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config2 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("READY")
|
||||
.state("S1")
|
||||
.state("S2")
|
||||
.state("S3", "E1", "E2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("READY").target("S1")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("S2")
|
||||
.event("E2")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("S3")
|
||||
.event("E3")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S3").target("S1")
|
||||
.event("E4")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S3").target("S2")
|
||||
.event("E5")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S3").target("READY")
|
||||
.event("E6");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config3 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("READY")
|
||||
.state("SUB1")
|
||||
.state("SUB2")
|
||||
.state("SUB3")
|
||||
.state("SUB4")
|
||||
.state("SUB5")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB1")
|
||||
.initial("SUB11")
|
||||
.state("SUB12", "E15")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB2")
|
||||
.initial("SUB21")
|
||||
.state("SUB22")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB3")
|
||||
.initial("SUB31")
|
||||
.state("SUB32");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("READY").target("SUB1")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("SUB2")
|
||||
.event("E2")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("SUB3")
|
||||
.event("E3")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("SUB4")
|
||||
.event("E4")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("SUB5")
|
||||
.event("E5")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB1").target("SUB5")
|
||||
.event("E15")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB5").target("SUB1")
|
||||
.event("E51")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB11").target("SUB12")
|
||||
.event("E1112")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB12").target("SUB11")
|
||||
.event("E1211");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config4 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("READY")
|
||||
.state("SUB1")
|
||||
.state("SUB2")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB1")
|
||||
.initial("SUB111")
|
||||
.state("SUB112", "E3", "E6")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("SUB1")
|
||||
.initial("SUB121")
|
||||
.state("SUB122", "E3", "E7");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("READY").target("SUB1")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB1").target("SUB2")
|
||||
.event("E2")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB1").target("SUB2")
|
||||
.event("E3")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB1").target("SUB2")
|
||||
.event("E4")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB111").target("SUB112")
|
||||
.event("E5")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("SUB121").target("SUB122")
|
||||
.event("E8");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ExecutorConfig {
|
||||
|
||||
@Bean(name=StateMachineSystemConstants.TASK_EXECUTOR_BEAN_NAME)
|
||||
public TaskExecutor taskExecutor() {
|
||||
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
|
||||
taskExecutor.setCorePoolSize(1);
|
||||
taskExecutor.setMaxPoolSize(1);
|
||||
return taskExecutor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestListener extends StateMachineListenerAdapter<String, String> {
|
||||
|
||||
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||
volatile CountDownLatch stateMachineStartedLatch = new CountDownLatch(1);
|
||||
volatile CountDownLatch readyStateEnteredLatch = new CountDownLatch(1);
|
||||
volatile CountDownLatch sub3readyStateEnteredLatch = new CountDownLatch(1);
|
||||
volatile int readyStateEnteredCount = 0;
|
||||
volatile int sub3readyStateEnteredCount = 0;
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<String, String> from, State<String, String> to) {
|
||||
stateChangedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateEntered(State<String, String> state) {
|
||||
if (state.getId().equals("READY")) {
|
||||
readyStateEnteredCount++;
|
||||
readyStateEnteredLatch.countDown();
|
||||
} else if (state.getId().equals("SUB3READY")) {
|
||||
sub3readyStateEnteredCount++;
|
||||
sub3readyStateEnteredLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateMachineStarted(StateMachine<String, String> stateMachine) {
|
||||
stateMachineStartedLatch.countDown();
|
||||
}
|
||||
|
||||
public void reset(int c1, int c2, int c3, int c4) {
|
||||
stateChangedLatch = new CountDownLatch(c1);
|
||||
stateMachineStartedLatch = new CountDownLatch(c2);
|
||||
readyStateEnteredLatch = new CountDownLatch(c3);
|
||||
sub3readyStateEnteredLatch = new CountDownLatch(c4);
|
||||
readyStateEnteredCount = 0;
|
||||
sub3readyStateEnteredCount = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -15,18 +15,29 @@
|
||||
*/
|
||||
package org.springframework.statemachine.docs;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineBuilder;
|
||||
@@ -35,6 +46,8 @@ import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@@ -43,6 +56,11 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
public class DocsConfigurationSampleTests2 extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
// tag::snippetA[]
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
@@ -169,4 +187,203 @@ public class DocsConfigurationSampleTests2 extends AbstractStateMachineTests {
|
||||
}
|
||||
// end::snippetD[]
|
||||
|
||||
@Test
|
||||
public void testConfig51() throws Exception {
|
||||
context.register(Config5.class, ExecutorConfig.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(1));
|
||||
listener.reset(0, 0, 2);
|
||||
machine.sendEvent("DEPLOY");
|
||||
machine.sendEvent("DEPLOY");
|
||||
assertThat(listener.readyStateEnteredLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfig52() throws Exception {
|
||||
context.register(Config5.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(1));
|
||||
listener.reset(0, 0, 2);
|
||||
machine.sendEvent("DEPLOY");
|
||||
machine.sendEvent("DEPLOY");
|
||||
assertThat(listener.readyStateEnteredLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfig61() throws Exception {
|
||||
context.register(Config6.class, ExecutorConfig.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(1));
|
||||
listener.reset(0, 0, 2);
|
||||
machine.sendEvent("DEPLOY");
|
||||
machine.sendEvent("DEPLOY");
|
||||
assertThat(listener.readyStateEnteredLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfig62() throws Exception {
|
||||
context.register(Config6.class);
|
||||
context.refresh();
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(1));
|
||||
listener.reset(0, 0, 2);
|
||||
machine.sendEvent("DEPLOY");
|
||||
machine.sendEvent("DEPLOY");
|
||||
assertThat(listener.readyStateEnteredLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.readyStateEnteredCount, is(2));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ExecutorConfig {
|
||||
|
||||
@Bean(name=StateMachineSystemConstants.TASK_EXECUTOR_BEAN_NAME)
|
||||
public TaskExecutor taskExecutor() {
|
||||
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
|
||||
taskExecutor.setCorePoolSize(1);
|
||||
return taskExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
// tag::snippetE[]
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config5 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||
throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("READY")
|
||||
.state("DEPLOYPREPARE", "DEPLOY")
|
||||
.state("DEPLOYEXECUTE", "DEPLOY");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||
throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("READY").target("DEPLOYPREPARE")
|
||||
.event("DEPLOY")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("DEPLOYPREPARE").target("DEPLOYEXECUTE")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("DEPLOYEXECUTE").target("READY");
|
||||
}
|
||||
}
|
||||
// end::snippetE[]
|
||||
|
||||
// tag::snippetF[]
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config6 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||
throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("READY")
|
||||
.state("DEPLOY", "DEPLOY")
|
||||
.state("DONE")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("DEPLOY")
|
||||
.initial("DEPLOYPREPARE")
|
||||
.state("DEPLOYPREPARE", "DONE")
|
||||
.state("DEPLOYEXECUTE");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||
throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("READY").target("DEPLOY")
|
||||
.event("DEPLOY")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("DEPLOYPREPARE").target("DEPLOYEXECUTE")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("DEPLOYEXECUTE").target("READY")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("READY").target("DONE")
|
||||
.event("DONE")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("DEPLOY").target("DONE")
|
||||
.event("DONE");
|
||||
}
|
||||
}
|
||||
// end::snippetF[]
|
||||
|
||||
static class TestListener extends StateMachineListenerAdapter<String, String> {
|
||||
|
||||
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||
volatile CountDownLatch stateMachineStartedLatch = new CountDownLatch(1);
|
||||
volatile CountDownLatch readyStateEnteredLatch = new CountDownLatch(1);
|
||||
volatile int readyStateEnteredCount = 0;
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<String, String> from, State<String, String> to) {
|
||||
stateChangedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateEntered(State<String, String> state) {
|
||||
if (state.getId().equals("READY")) {
|
||||
readyStateEnteredCount++;
|
||||
readyStateEnteredLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateMachineStarted(StateMachine<String, String> stateMachine) {
|
||||
stateMachineStartedLatch.countDown();
|
||||
}
|
||||
|
||||
public void reset(int c1, int c2, int c3) {
|
||||
stateChangedLatch = new CountDownLatch(c1);
|
||||
stateMachineStartedLatch = new CountDownLatch(c2);
|
||||
readyStateEnteredLatch = new CountDownLatch(c3);
|
||||
readyStateEnteredCount = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user