diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java index c4760bfb..fd11efa3 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -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, E extends Enum> exten S source = transitionData.getSource(); S target = transitionData.getTarget(); E event = transitionData.getEvent(); + Long period = transitionData.getPeriod(); + + Trigger trigger = null; + if (event != null) { + trigger = new EventTrigger(event); + } else if (period != null) { + TimerTrigger t = new TimerTrigger(period); + if (beanFactory != null) { + t.setBeanFactory(beanFactory); + } + trigger = t; + } + if (transitionData.getKind() == TransitionKind.EXTERNAL) { DefaultExternalTransition transition = new DefaultExternalTransition(stateMap.get(source), - stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), event != null ? new EventTrigger(event) : null); + stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger); transitions.add(transition); } else if (transitionData.getKind() == TransitionKind.INTERNAL) { DefaultInternalTransition transition = new DefaultInternalTransition(stateMap.get(source), - transitionData.getActions(), event, transitionData.getGuard(), event != null ? new EventTrigger(event) : null); + transitionData.getActions(), event, transitionData.getGuard(), trigger); transitions.add(transition); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java index ece046ca..dcbd2f82 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java @@ -72,8 +72,8 @@ public class StateMachineTransitionBuilder return apply(new DefaultLocalTransitionConfigurer()); } - public void add(S source, S target, S state, E event, Collection> actions, Guard guard, TransitionKind kind) { - transitionData.add(new TransitionData(source, target, state, event, actions, guard, kind)); + public void add(S source, S target, S state, E event, Long period, Collection> actions, Guard guard, TransitionKind kind) { + transitionData.add(new TransitionData(source, target, state, event, period, actions, guard, kind)); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java index 1d3200a0..7b45ea80 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java @@ -38,14 +38,16 @@ public class StateMachineTransitions { S target; S state; E event; + Long period; Collection> actions; Guard guard; TransitionKind kind; - public TransitionData(S source, S target, S state, E event, Collection> actions, Guard guard, TransitionKind kind) { + public TransitionData(S source, S target, S state, E event, Long period, Collection> actions, Guard 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 { public E getEvent() { return event; } + public Long getPeriod() { + return period; + } public Collection> getActions() { return actions; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java index 56ca070c..5c846e3e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java @@ -50,13 +50,15 @@ public class DefaultExternalTransitionConfigurer private E event; + private Long period; + private Collection> actions = new ArrayList>(); private Guard guard; @Override public void configure(StateMachineTransitionBuilder 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 return this; } + @Override + public ExternalTransitionConfigurer timer(long period) { + this.period = period; + return this; + } + @Override public ExternalTransitionConfigurer action(Action action) { actions.add(action); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java index c1d00650..c83831e2 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java @@ -50,13 +50,15 @@ public class DefaultInternalTransitionConfigurer private E event; + private Long period; + private Collection> actions = new ArrayList>(); private Guard guard; @Override public void configure(StateMachineTransitionBuilder 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 return this; } + @Override + public InternalTransitionConfigurer timer(long period) { + this.period = period; + return this; + } + @Override public InternalTransitionConfigurer action(Action action) { actions.add(action); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java index 0b872757..5cbeb4e9 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java @@ -50,13 +50,15 @@ public class DefaultLocalTransitionConfigurer private E event; + private Long period; + private Collection> actions = new ArrayList>(); private Guard guard; @Override public void configure(StateMachineTransitionBuilder 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 return this; } + @Override + public LocalTransitionConfigurer timer(long period) { + this.period = period; + return this; + } + @Override public LocalTransitionConfigurer action(Action action) { actions.add(action); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java index a1815121..a973432d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java @@ -51,6 +51,8 @@ public interface TransitionConfigurer extends */ T event(E event); + T timer(long period); + /** * Specify {@link Action} for this {@link Transition}. * diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index 6fc87871..932cb945 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -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 extends LifecycleObjectSupport @Override protected void doStart() { super.doStart(); + registerTriggerListener(); switchToState(initialState, initialEvent, null); } @@ -378,11 +382,17 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport while ((queueItem = triggerQueue.poll()) != null) { Message queuedEvent = queueItem.message; Transition transition = triggerToTransitionMap.get(queueItem.trigger); - StateContext stateContext = new DefaultStateContext(queuedEvent.getHeaders(), extendedState, transition); - notifyTransitionStart(transition); + StateContext stateContext = new DefaultStateContext(queuedEvent != null ? queuedEvent.getHeaders() : null, extendedState, transition); if (transition == null) { continue; } + + State 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 extends LifecycleObjectSupport return handlersList; } + private void registerTriggerListener() { + for (final Trigger 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 source, State target) { stateListener.stateChanged(source, target); StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/CompositeTriggerListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/CompositeTriggerListener.java new file mode 100644 index 00000000..63b1669d --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/CompositeTriggerListener.java @@ -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 implements TriggerListener { + + @Override + public void triggered() { + for (Iterator iterator = getListeners().reverse(); iterator.hasNext();) { + TriggerListener listener = iterator.next(); + listener.triggered(); + } + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java index 0e6ee021..67d83ccf 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/EventTrigger.java @@ -30,4 +30,9 @@ public class EventTrigger implements Trigger { return ObjectUtils.nullSafeEquals(event, context.getEvent()); } + @Override + public void addTriggerListener(TriggerListener listener) { + // no-opt + } + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/TimerTrigger.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/TimerTrigger.java new file mode 100644 index 00000000..944f8b32 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/TimerTrigger.java @@ -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 extends LifecycleObjectSupport implements Trigger { + + 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 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(); + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java index 0b5f7e88..ae482333 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/Trigger.java @@ -36,4 +36,11 @@ public interface Trigger { */ boolean evaluate(TriggerContext context); + /** + * Adds the trigger listener. + * + * @param listener the listener + */ + void addTriggerListener(TriggerListener listener); + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/TriggerListener.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/TriggerListener.java new file mode 100644 index 00000000..44b3a302 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/trigger/TriggerListener.java @@ -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(); + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java index 116accb6..4534ce68 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java @@ -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 { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java index 77581dc7..c2b03787 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java @@ -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 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 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 { @@ -70,7 +119,7 @@ public class StateMachineTests extends AbstractStateMachineTests { @Configuration @EnableStateMachine - static class Config extends EnumStateMachineConfigurerAdapter { + static class Config1 extends EnumStateMachineConfigurerAdapter { @Override public void configure(StateMachineStateConfigurer states) throws Exception { @@ -124,4 +173,68 @@ public class StateMachineTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachine + static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer 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 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(); + } + + } + } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/trigger/TimerTriggerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/trigger/TimerTriggerTests.java new file mode 100644 index 00000000..9004958f --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/trigger/TimerTriggerTests.java @@ -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 timerTrigger() { + return new TimerTrigger(100); + } + } + +}