Pass event headers into triggerless transitions

- Change DefaultStateMachineExecutor to keep event message
  around and then loop triggerless transitions order to
  pass that message to whole chain.
- Fixes #100
This commit is contained in:
Janne Valkealahti
2015-08-29 10:31:10 +01:00
parent 022143b5ce
commit a6145072b5
2 changed files with 163 additions and 5 deletions

View File

@@ -173,7 +173,8 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
interceptors.add(interceptor);
}
private void handleTriggerTrans(List<Transition<S, E>> trans, Message<E> queuedMessage) {
private boolean handleTriggerTrans(List<Transition<S, E>> trans, Message<E> queuedMessage) {
boolean transit = false;
for (Transition<S, E> t : trans) {
if (t == null) {
continue;
@@ -183,6 +184,9 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
continue;
}
State<S,E> currentState = stateMachine.getState();
if (currentState == null) {
continue;
}
if (!StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
continue;
}
@@ -193,13 +197,14 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
break;
}
boolean transit = t.transit(stateContext);
transit = t.transit(stateContext);
if (transit) {
stateMachineExecutorTransit.transit(t, stateContext, queuedMessage);
interceptors.postTransition(stateContext);
break;
}
}
return transit;
}
private void handleInitialTrans(Transition<S, E> tran, Message<E> queuedMessage) {
@@ -282,6 +287,9 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
}
log.debug("Process trigger queue");
TriggerQueueItem queueItem = null;
// keep last message here so that we can
// pass it to triggerless transitions
Message<E> queuedMessage = null;
while ((queueItem = triggerQueue.poll()) != null) {
State<S,E> currentState = stateMachine.getState();
@@ -290,7 +298,7 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
continue;
}
Message<E> queuedMessage = queueItem.message;
queuedMessage = queueItem.message;
E event = queuedMessage != null ? queuedMessage.getPayload() : null;
// need all transitions trigger could match, event trigger may match
@@ -326,8 +334,12 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
handleTriggerTrans(trans, queuedMessage);
}
if (stateMachine.getState() != null) {
// handle triggerless transitions
handleTriggerTrans(triggerlessTransitions, null);
// loop triggerless transitions here so that
// all "chained" transitions will get queue message
boolean transit = false;
do {
transit = handleTriggerTrans(triggerlessTransitions, queuedMessage);
} while (transit);
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.transition;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
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.messaging.support.MessageBuilder;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.ObjectStateMachine;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachineSystemConstants;
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.transition.TransitionTests.TestListener;
public class TransitionEventHeaderTests extends AbstractStateMachineTests {
@Override
protected AnnotationConfigApplicationContext buildContext() {
return new AnnotationConfigApplicationContext();
}
@SuppressWarnings("unchecked")
@Test
public void testEventPassedOnWithTrigggerless() throws Exception {
context.register(Config1.class);
context.refresh();
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
EventCheckAction eventCheckAction1 = context.getBean("eventCheckAction1", EventCheckAction.class);
EventCheckAction eventCheckAction2 = context.getBean("eventCheckAction2", EventCheckAction.class);
EventCheckAction eventCheckAction3 = context.getBean("eventCheckAction3", EventCheckAction.class);
EventCheckAction eventCheckAction4 = context.getBean("eventCheckAction4", EventCheckAction.class);
TestListener listener = new TestListener();
machine.addStateListener(listener);
machine.start();
assertThat(machine.getState().getIds(), contains(TestStates.S1));
listener.reset(3);
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(listener.stateChangedCount, is(3));
assertThat(machine.getState().getIds(), contains(TestStates.S4));
assertThat(eventCheckAction1.context.getEvent(), nullValue());
assertThat(eventCheckAction2.context.getEvent(), is(TestEvents.E1));
assertThat(eventCheckAction3.context.getEvent(), is(TestEvents.E1));
assertThat(eventCheckAction4.context.getEvent(), is(TestEvents.E1));
}
@Configuration
@EnableStateMachine
public static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.state(TestStates.S1, eventCheckAction1(), null)
.state(TestStates.S2, eventCheckAction2(), null)
.state(TestStates.S3, eventCheckAction3(), null)
.state(TestStates.S4, eventCheckAction4(), null);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1)
.and()
.withExternal()
.source(TestStates.S2)
.target(TestStates.S3)
.and()
.withExternal()
.source(TestStates.S3)
.target(TestStates.S4);
}
@Bean
public EventCheckAction eventCheckAction1() {
return new EventCheckAction();
}
@Bean
public EventCheckAction eventCheckAction2() {
return new EventCheckAction();
}
@Bean
public EventCheckAction eventCheckAction3() {
return new EventCheckAction();
}
@Bean
public EventCheckAction eventCheckAction4() {
return new EventCheckAction();
}
}
private static class EventCheckAction implements Action<TestStates, TestEvents> {
StateContext<TestStates, TestEvents> context;
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
this.context = context;
}
}
}