Add TimerTrigger
- This fixes #gh21 - New TimerTrigger which can be used to schedule triggers which then may cause transitions.
This commit is contained in:
@@ -45,6 +45,8 @@ import org.springframework.statemachine.transition.DefaultInternalTransition;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
import org.springframework.statemachine.trigger.EventTrigger;
|
||||
import org.springframework.statemachine.trigger.TimerTrigger;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
@@ -289,14 +291,27 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
S source = transitionData.getSource();
|
||||
S target = transitionData.getTarget();
|
||||
E event = transitionData.getEvent();
|
||||
Long period = transitionData.getPeriod();
|
||||
|
||||
Trigger<S, E> trigger = null;
|
||||
if (event != null) {
|
||||
trigger = new EventTrigger<S, E>(event);
|
||||
} else if (period != null) {
|
||||
TimerTrigger<S, E> t = new TimerTrigger<S, E>(period);
|
||||
if (beanFactory != null) {
|
||||
t.setBeanFactory(beanFactory);
|
||||
}
|
||||
trigger = t;
|
||||
}
|
||||
|
||||
if (transitionData.getKind() == TransitionKind.EXTERNAL) {
|
||||
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(stateMap.get(source),
|
||||
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), event != null ? new EventTrigger<S, E>(event) : null);
|
||||
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger);
|
||||
transitions.add(transition);
|
||||
|
||||
} else if (transitionData.getKind() == TransitionKind.INTERNAL) {
|
||||
DefaultInternalTransition<S, E> transition = new DefaultInternalTransition<S, E>(stateMap.get(source),
|
||||
transitionData.getActions(), event, transitionData.getGuard(), event != null ? new EventTrigger<S, E>(event) : null);
|
||||
transitionData.getActions(), event, transitionData.getGuard(), trigger);
|
||||
transitions.add(transition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
return apply(new DefaultLocalTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
public void add(S source, S target, S state, E event, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, actions, guard, kind));
|
||||
public void add(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, actions, guard, kind));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,14 +38,16 @@ public class StateMachineTransitions<S, E> {
|
||||
S target;
|
||||
S state;
|
||||
E event;
|
||||
Long period;
|
||||
Collection<Action<S, E>> actions;
|
||||
Guard<S, E> guard;
|
||||
TransitionKind kind;
|
||||
public TransitionData(S source, S target, S state, E event, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
public TransitionData(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.state = state;
|
||||
this.event = event;
|
||||
this.period = period;
|
||||
this.actions = actions;
|
||||
this.guard = guard;
|
||||
this.kind = kind;
|
||||
@@ -62,6 +64,9 @@ public class StateMachineTransitions<S, E> {
|
||||
public E getEvent() {
|
||||
return event;
|
||||
}
|
||||
public Long getPeriod() {
|
||||
return period;
|
||||
}
|
||||
public Collection<Action<S, E>> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
@@ -50,13 +50,15 @@ public class DefaultExternalTransitionConfigurer<S, E>
|
||||
|
||||
private E event;
|
||||
|
||||
private Long period;
|
||||
|
||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||
|
||||
private Guard<S, E> guard;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(source, target, state, event, actions, guard, TransitionKind.EXTERNAL);
|
||||
builder.add(source, target, state, event, period, actions, guard, TransitionKind.EXTERNAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,6 +85,12 @@ public class DefaultExternalTransitionConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> timer(long period) {
|
||||
this.period = period;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||
actions.add(action);
|
||||
|
||||
@@ -50,13 +50,15 @@ public class DefaultInternalTransitionConfigurer<S, E>
|
||||
|
||||
private E event;
|
||||
|
||||
private Long period;
|
||||
|
||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||
|
||||
private Guard<S, E> guard;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(source, target, state, event, actions, guard, TransitionKind.INTERNAL);
|
||||
builder.add(source, target, state, event, period, actions, guard, TransitionKind.INTERNAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,6 +79,12 @@ public class DefaultInternalTransitionConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> timer(long period) {
|
||||
this.period = period;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||
actions.add(action);
|
||||
|
||||
@@ -50,13 +50,15 @@ public class DefaultLocalTransitionConfigurer<S, E>
|
||||
|
||||
private E event;
|
||||
|
||||
private Long period;
|
||||
|
||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||
|
||||
private Guard<S, E> guard;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(source, target, state, event, actions, guard, TransitionKind.LOCAL);
|
||||
builder.add(source, target, state, event, period, actions, guard, TransitionKind.LOCAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,6 +85,12 @@ public class DefaultLocalTransitionConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> timer(long period) {
|
||||
this.period = period;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||
actions.add(action);
|
||||
|
||||
@@ -51,6 +51,8 @@ public interface TransitionConfigurer<T, S, E> extends
|
||||
*/
|
||||
T event(E event);
|
||||
|
||||
T timer(long period);
|
||||
|
||||
/**
|
||||
* Specify {@link Action} for this {@link Transition}.
|
||||
*
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
@@ -50,7 +51,9 @@ import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
import org.springframework.statemachine.trigger.DefaultTriggerContext;
|
||||
import org.springframework.statemachine.trigger.TimerTrigger;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
import org.springframework.statemachine.trigger.TriggerListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -204,6 +207,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
@Override
|
||||
protected void doStart() {
|
||||
super.doStart();
|
||||
registerTriggerListener();
|
||||
switchToState(initialState, initialEvent, null);
|
||||
}
|
||||
|
||||
@@ -378,11 +382,17 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
while ((queueItem = triggerQueue.poll()) != null) {
|
||||
Message<E> queuedEvent = queueItem.message;
|
||||
Transition<S, E> transition = triggerToTransitionMap.get(queueItem.trigger);
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(queuedEvent.getHeaders(), extendedState, transition);
|
||||
notifyTransitionStart(transition);
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(queuedEvent != null ? queuedEvent.getHeaders() : null, extendedState, transition);
|
||||
if (transition == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
State<S,E> source = transition.getSource();
|
||||
if (!StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
notifyTransitionStart(transition);
|
||||
boolean transit = transition.transit(stateContext);
|
||||
if (transit && transition.getKind() != TransitionKind.INTERNAL) {
|
||||
switchToState(transition.getTarget(), queuedEvent, transition);
|
||||
@@ -459,6 +469,24 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
return handlersList;
|
||||
}
|
||||
|
||||
private void registerTriggerListener() {
|
||||
for (final Trigger<S, E> trigger : triggerToTransitionMap.keySet()) {
|
||||
if (trigger instanceof TimerTrigger) {
|
||||
((TimerTrigger<?, ?>)trigger).addTriggerListener(new TriggerListener() {
|
||||
@Override
|
||||
public void triggered() {
|
||||
log.debug("TimedTrigger triggered " + trigger);
|
||||
triggerQueue.add(new TriggerQueueItem(trigger, null));
|
||||
scheduleEventQueueProcessing();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (trigger instanceof Lifecycle) {
|
||||
((Lifecycle)trigger).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyStateChanged(State<S,E> source, State<S,E> target) {
|
||||
stateListener.stateChanged(source, target);
|
||||
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.trigger;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.statemachine.listener.AbstractCompositeListener;
|
||||
|
||||
/**
|
||||
* Default {@link TriggerListener} dispatcher.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class CompositeTriggerListener extends AbstractCompositeListener<TriggerListener> implements TriggerListener {
|
||||
|
||||
@Override
|
||||
public void triggered() {
|
||||
for (Iterator<TriggerListener> iterator = getListeners().reverse(); iterator.hasNext();) {
|
||||
TriggerListener listener = iterator.next();
|
||||
listener.triggered();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,4 +30,9 @@ public class EventTrigger<S, E> implements Trigger<S, E> {
|
||||
return ObjectUtils.nullSafeEquals(event, context.getEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTriggerListener(TriggerListener listener) {
|
||||
// no-opt
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.trigger;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.springframework.statemachine.support.LifecycleObjectSupport;
|
||||
|
||||
public class TimerTrigger<S, E> extends LifecycleObjectSupport implements Trigger<S, E> {
|
||||
|
||||
private final CompositeTriggerListener triggerListener = new CompositeTriggerListener();
|
||||
|
||||
private final long period;
|
||||
|
||||
private volatile ScheduledFuture<?> scheduled;
|
||||
|
||||
public TimerTrigger(long period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(TriggerContext<S, E> context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTriggerListener(TriggerListener listener) {
|
||||
triggerListener.register(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
scheduled = getTaskScheduler().scheduleAtFixedRate(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
notifyTriggered();
|
||||
}
|
||||
}, period);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
if (scheduled != null) {
|
||||
scheduled.cancel(true);
|
||||
}
|
||||
scheduled = null;
|
||||
}
|
||||
|
||||
private void notifyTriggered() {
|
||||
triggerListener.triggered();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,4 +36,11 @@ public interface Trigger<S,E> {
|
||||
*/
|
||||
boolean evaluate(TriggerContext<S, E> context);
|
||||
|
||||
/**
|
||||
* Adds the trigger listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void addTriggerListener(TriggerListener listener);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.trigger;
|
||||
|
||||
/**
|
||||
* {@code TriggerListener} for listening a {@link Trigger} events.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public interface TriggerListener {
|
||||
|
||||
/**
|
||||
* Notified when trigger has been triggered.
|
||||
*/
|
||||
void triggered();
|
||||
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
@@ -79,6 +81,11 @@ public abstract class AbstractStateMachineTests {
|
||||
return new SyncTaskExecutor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskScheduler taskScheduler() {
|
||||
return new ConcurrentTaskScheduler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestEntryAction extends AbstractTestAction {
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package org.springframework.statemachine;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Test;
|
||||
@@ -36,19 +39,65 @@ import org.springframework.statemachine.config.builders.StateMachineTransitionCo
|
||||
|
||||
public class StateMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoggingEvents() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
|
||||
assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||
context.register(Config1.class);
|
||||
context.refresh();
|
||||
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||
@SuppressWarnings("unchecked")
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee1").build());
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).setHeader("foo", "jee2").build());
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).setHeader("foo", "jee2").build());
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimerTransition() throws Exception {
|
||||
context.register(BaseConfig.class, Config2.class);
|
||||
context.refresh();
|
||||
|
||||
TestAction testAction1 = context.getBean("testAction1", TestAction.class);
|
||||
TestAction testAction2 = context.getBean("testAction2", TestAction.class);
|
||||
TestAction testAction3 = context.getBean("testAction3", TestAction.class);
|
||||
TestAction testAction4 = context.getBean("testAction4", TestAction.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
|
||||
machine.start();
|
||||
Thread.sleep(2000);
|
||||
assertThat(testAction2.stateContexts.size(), is(0));
|
||||
machine.sendEvent(TestEvents.E1);
|
||||
assertThat(testAction1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(testAction1.stateContexts.size(), is(1));
|
||||
assertThat(testAction2.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
|
||||
assertThat(testAction2.stateContexts.size(), is(1));
|
||||
|
||||
machine.sendEvent(TestEvents.E2);
|
||||
assertThat(testAction3.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(testAction3.stateContexts.size(), is(1));
|
||||
|
||||
// timer still fires but should not cause transition anymore
|
||||
// after we sleep and do next event
|
||||
int timedTriggered = testAction2.stateContexts.size();
|
||||
Thread.sleep(2000);
|
||||
assertThat(testAction2.stateContexts.size(), is(timedTriggered));
|
||||
|
||||
machine.sendEvent(TestEvents.E3);
|
||||
assertThat(testAction4.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(testAction4.stateContexts.size(), is(1));
|
||||
|
||||
assertThat(testAction2.stateContexts.size(), is(timedTriggered));
|
||||
}
|
||||
|
||||
private static class LoggingAction implements Action<TestStates, TestEvents> {
|
||||
@@ -70,7 +119,7 @@ public class StateMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
@@ -124,4 +173,68 @@ public class StateMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config2 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S1)
|
||||
.state(TestStates.S2)
|
||||
.state(TestStates.S3)
|
||||
.state(TestStates.S4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S2)
|
||||
.event(TestEvents.E1)
|
||||
.action(testAction1())
|
||||
.and()
|
||||
.withInternal()
|
||||
.source(TestStates.S2)
|
||||
.timer(1000)
|
||||
.action(testAction2())
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S2)
|
||||
.target(TestStates.S3)
|
||||
.event(TestEvents.E2)
|
||||
.action(testAction3())
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S3)
|
||||
.target(TestStates.S4)
|
||||
.event(TestEvents.E3)
|
||||
.action(testAction4());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestAction testAction1() {
|
||||
return new TestAction();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestAction testAction2() {
|
||||
return new TestAction();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestAction testAction3() {
|
||||
return new TestAction();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestAction testAction4() {
|
||||
return new TestAction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.trigger;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
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.statemachine.AbstractStateMachineTests;
|
||||
|
||||
public class TimerTriggerTests extends AbstractStateMachineTests {
|
||||
|
||||
@Test
|
||||
public void testListenerEvents() throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class, Config1.class);
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
@SuppressWarnings("rawtypes")
|
||||
TimerTrigger timerTrigger = ctx.getBean(TimerTrigger.class);
|
||||
timerTrigger.addTriggerListener(new TriggerListener() {
|
||||
|
||||
@Override
|
||||
public void triggered() {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
timerTrigger.afterPropertiesSet();
|
||||
timerTrigger.start();
|
||||
|
||||
assertThat(latch.await(1, TimeUnit.SECONDS), is(true));
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
static class Config1 {
|
||||
|
||||
@Bean
|
||||
public TimerTrigger<TestStates, TestEvents> timerTrigger() {
|
||||
return new TimerTrigger<TestStates, TestEvents>(100);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user