Timer interval reset for states
- Add new features around Trigger to arm/disarm it so that state entry/exit can stop firing TimerTrigger. Default functionality for timer(long) still stays same, however timerOnce(long) will kick this new functionality where trigger will fire once after state has been entered. - Fixes #165
This commit is contained in:
@@ -51,6 +51,7 @@ import org.springframework.statemachine.ensemble.DistributedStateMachine;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.security.StateMachineSecurityInterceptor;
|
||||
import org.springframework.statemachine.state.AbstractState;
|
||||
import org.springframework.statemachine.state.ChoicePseudoState;
|
||||
import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData;
|
||||
import org.springframework.statemachine.state.DefaultPseudoState;
|
||||
@@ -547,12 +548,13 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
S target = transitionData.getTarget();
|
||||
E event = transitionData.getEvent();
|
||||
Long period = transitionData.getPeriod();
|
||||
Integer count = transitionData.getCount();
|
||||
|
||||
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);
|
||||
TimerTrigger<S, E> t = new TimerTrigger<S, E>(period, count != null ? count : 0);
|
||||
if (beanFactory != null) {
|
||||
t.setBeanFactory(beanFactory);
|
||||
}
|
||||
@@ -563,6 +565,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
t.setTaskScheduler(taskScheduler);
|
||||
}
|
||||
trigger = t;
|
||||
((AbstractState<S, E>)stateMap.get(source)).getTriggers().add(trigger);
|
||||
}
|
||||
|
||||
if (transitionData.getKind() == TransitionKind.EXTERNAL) {
|
||||
|
||||
@@ -111,7 +111,7 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
return apply(new DefaultJoinTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
public void add(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
||||
public void add(S source, S target, S state, E event, Long period, Integer count, Collection<Action<S, E>> actions,
|
||||
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
|
||||
// if rule not given, get it from global
|
||||
if (securityRule == null) {
|
||||
@@ -119,7 +119,7 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
StateMachineConfigurationConfig<S, E> config = getSharedObject(StateMachineConfigurationConfig.class);
|
||||
securityRule = config.getTransitionSecurityRule();
|
||||
}
|
||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, actions, guard, kind, securityRule));
|
||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, count, actions, guard, kind, securityRule));
|
||||
}
|
||||
|
||||
public void add(S source, List<ChoiceData<S, E>> choices) {
|
||||
|
||||
@@ -43,6 +43,7 @@ public abstract class AbstractTransitionConfigurer<S, E> extends
|
||||
private S state;
|
||||
private E event;
|
||||
private Long period;
|
||||
private Integer count;
|
||||
private final Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||
private Guard<S, E> guard;
|
||||
private SecurityRule securityRule;
|
||||
@@ -67,6 +68,10 @@ public abstract class AbstractTransitionConfigurer<S, E> extends
|
||||
return period;
|
||||
}
|
||||
|
||||
public Integer getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
protected Collection<Action<S, E>> getActions() {
|
||||
return actions;
|
||||
}
|
||||
@@ -99,6 +104,10 @@ public abstract class AbstractTransitionConfigurer<S, E> extends
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
public void setCount(Integer count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
protected void addAction(Action<S, E> action) {
|
||||
this.actions.add(action);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class DefaultExternalTransitionConfigurer<S, E> extends AbstractTransitio
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getActions(), getGuard(), TransitionKind.EXTERNAL,
|
||||
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.EXTERNAL,
|
||||
getSecurityRule());
|
||||
}
|
||||
|
||||
@@ -72,6 +72,13 @@ public class DefaultExternalTransitionConfigurer<S, E> extends AbstractTransitio
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> timerOnce(long period) {
|
||||
setPeriod(period);
|
||||
setCount(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||
addAction(action);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class DefaultInternalTransitionConfigurer<S, E> extends AbstractTransitio
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getActions(), getGuard(), TransitionKind.INTERNAL,
|
||||
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.INTERNAL,
|
||||
getSecurityRule());
|
||||
}
|
||||
|
||||
@@ -66,6 +66,13 @@ public class DefaultInternalTransitionConfigurer<S, E> extends AbstractTransitio
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> timerOnce(long period) {
|
||||
setPeriod(period);
|
||||
setCount(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||
addAction(action);
|
||||
|
||||
@@ -37,7 +37,7 @@ public class DefaultLocalTransitionConfigurer<S, E> extends AbstractTransitionCo
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getActions(), getGuard(), TransitionKind.LOCAL,
|
||||
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.LOCAL,
|
||||
getSecurityRule());
|
||||
}
|
||||
|
||||
@@ -71,6 +71,13 @@ public class DefaultLocalTransitionConfigurer<S, E> extends AbstractTransitionCo
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> timerOnce(long period) {
|
||||
setPeriod(period);
|
||||
setCount(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||
addAction(action);
|
||||
|
||||
@@ -67,6 +67,14 @@ public interface TransitionConfigurer<T, S, E> extends
|
||||
*/
|
||||
T timer(long period);
|
||||
|
||||
/**
|
||||
* Specify that this transition is triggered once by a time after a delay.
|
||||
*
|
||||
* @param period timer period in millis
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
T timerOnce(long period);
|
||||
|
||||
/**
|
||||
* Specify {@link Action} for this {@link Transition}.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2016 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.
|
||||
@@ -34,6 +34,7 @@ public class TransitionData<S, E> {
|
||||
private final S state;
|
||||
private final E event;
|
||||
private final Long period;
|
||||
private final Integer count;
|
||||
private final Collection<Action<S, E>> actions;
|
||||
private final Guard<S, E> guard;
|
||||
private final TransitionKind kind;
|
||||
@@ -47,18 +48,20 @@ public class TransitionData<S, E> {
|
||||
* @param state the state
|
||||
* @param event the event
|
||||
* @param period the period
|
||||
* @param count the count
|
||||
* @param actions the actions
|
||||
* @param guard the guard
|
||||
* @param kind the kind
|
||||
* @param securityRule the security rule
|
||||
*/
|
||||
public TransitionData(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
||||
public TransitionData(S source, S target, S state, E event, Long period, Integer count, Collection<Action<S, E>> actions,
|
||||
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.state = state;
|
||||
this.event = event;
|
||||
this.period = period;
|
||||
this.count = count;
|
||||
this.actions = actions;
|
||||
this.guard = guard;
|
||||
this.kind = kind;
|
||||
@@ -110,6 +113,15 @@ public class TransitionData<S, E> {
|
||||
return period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the count.
|
||||
*
|
||||
* @return the count
|
||||
*/
|
||||
public Integer getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the actions.
|
||||
*
|
||||
|
||||
@@ -17,12 +17,14 @@ package org.springframework.statemachine.state;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link State}.
|
||||
@@ -41,6 +43,7 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
private final Collection<? extends Action<S, E>> exitActions;
|
||||
private final Collection<Region<S, E>> regions = new ArrayList<Region<S, E>>();
|
||||
private final StateMachine<S, E> submachine;
|
||||
private List<Trigger<S, E>> triggers = new ArrayList<Trigger<S, E>>();
|
||||
|
||||
/**
|
||||
* Instantiates a new abstract state.
|
||||
@@ -158,10 +161,18 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void exit(StateContext<S, E> context);
|
||||
public void exit(StateContext<S, E> context) {
|
||||
for (Trigger<S, E> trigger : triggers) {
|
||||
trigger.disarm();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void entry(StateContext<S, E> context);
|
||||
public void entry(StateContext<S, E> context) {
|
||||
for (Trigger<S, E> trigger : triggers) {
|
||||
trigger.arm();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public S getId() {
|
||||
@@ -232,6 +243,14 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
return regions;
|
||||
}
|
||||
|
||||
public void setTriggers(List<Trigger<S, E>> triggers) {
|
||||
this.triggers = triggers;
|
||||
}
|
||||
|
||||
public List<Trigger<S, E>> getTriggers() {
|
||||
return triggers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AbstractState [id=" + id + ", pseudoState=" + pseudoState + ", deferred=" + deferred
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2016 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.state;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Enhanced implementation following same logic from {@link PeriodicTrigger}
|
||||
* except also adding a counter how many times a trigger can fire. If given
|
||||
* count is either zero on a negative value, counter functionality is disabled.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
* @see PeriodicTrigger
|
||||
*/
|
||||
public class CountTrigger implements Trigger {
|
||||
|
||||
private final int count;
|
||||
private final long period;
|
||||
private final TimeUnit timeUnit;
|
||||
private volatile long initialDelay = 0;
|
||||
private volatile boolean fixedRate = false;
|
||||
private volatile int counter = 0;
|
||||
|
||||
/**
|
||||
* Create a trigger with the given period in milliseconds and firing
|
||||
* exactly one time.
|
||||
*
|
||||
* @param period the period
|
||||
*/
|
||||
public CountTrigger(long period) {
|
||||
this(1, period, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a trigger with the given count, period and time unit. The time unit will
|
||||
* apply not only to the period but also to any 'initialDelay' value, if
|
||||
* configured on this Trigger later via {@link #setInitialDelay(long)}.
|
||||
*
|
||||
* @param count the count
|
||||
* @param period the period
|
||||
* @param timeUnit the time unit
|
||||
*/
|
||||
public CountTrigger(int count, long period, TimeUnit timeUnit) {
|
||||
Assert.isTrue(period >= 0, "period must not be negative");
|
||||
Assert.isTrue(count >= 0, "count must not be negative");
|
||||
this.timeUnit = (timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS);
|
||||
this.period = this.timeUnit.toMillis(period);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the delay for the initial execution. It will be evaluated in
|
||||
* terms of this trigger's {@link TimeUnit}. If no time unit was explicitly
|
||||
* provided upon instantiation, the default is milliseconds.
|
||||
*
|
||||
* @param initialDelay the new initial delay
|
||||
*/
|
||||
public void setInitialDelay(long initialDelay) {
|
||||
this.initialDelay = this.timeUnit.toMillis(initialDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether the periodic interval should be measured between the
|
||||
* scheduled start times rather than between actual completion times.
|
||||
* The latter, "fixed delay" behavior, is the default.
|
||||
*
|
||||
* @param fixedRate the new fixed rate
|
||||
*/
|
||||
public void setFixedRate(boolean fixedRate) {
|
||||
this.fixedRate = fixedRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
if (count > 0) {
|
||||
if (++counter > count) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (triggerContext.lastScheduledExecutionTime() == null) {
|
||||
return new Date(System.currentTimeMillis() + this.initialDelay);
|
||||
}
|
||||
else if (this.fixedRate) {
|
||||
return new Date(triggerContext.lastScheduledExecutionTime().getTime() + this.period);
|
||||
}
|
||||
return new Date(triggerContext.lastCompletionTime().getTime() + this.period);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + count;
|
||||
result = prime * result + (fixedRate ? 1231 : 1237);
|
||||
result = prime * result + (int) (initialDelay ^ (initialDelay >>> 32));
|
||||
result = prime * result + (int) (period ^ (period >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
CountTrigger other = (CountTrigger) obj;
|
||||
if (count != other.count)
|
||||
return false;
|
||||
if (fixedRate != other.fixedRate)
|
||||
return false;
|
||||
if (initialDelay != other.initialDelay)
|
||||
return false;
|
||||
if (period != other.period)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,7 @@ public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
||||
|
||||
@Override
|
||||
public void exit(StateContext<S, E> context) {
|
||||
super.exit(context);
|
||||
Collection<? extends Action<S, E>> actions = getExitActions();
|
||||
if (actions != null) {
|
||||
for (Action<S, E> action : actions) {
|
||||
@@ -137,6 +138,7 @@ public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
||||
|
||||
@Override
|
||||
public void entry(StateContext<S, E> context) {
|
||||
super.entry(context);
|
||||
Collection<? extends Action<S, E>> actions = getEntryActions();
|
||||
if (actions != null) {
|
||||
for (Action<S, E> action : actions) {
|
||||
|
||||
@@ -416,7 +416,9 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
((TimerTrigger<?, ?>) trigger).addTriggerListener(new TriggerListener() {
|
||||
@Override
|
||||
public void triggered() {
|
||||
log.debug("TimedTrigger triggered " + trigger);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("TimedTrigger triggered " + trigger);
|
||||
}
|
||||
triggerQueue.add(new TriggerQueueItem(trigger, null));
|
||||
scheduleEventQueueProcessing();
|
||||
}
|
||||
|
||||
@@ -40,4 +40,13 @@ public class EventTrigger<S, E> implements Trigger<S, E> {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arm() {
|
||||
// no-opt
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disarm() {
|
||||
// no-opt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2016 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.
|
||||
@@ -16,19 +16,45 @@
|
||||
package org.springframework.statemachine.trigger;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.statemachine.state.CountTrigger;
|
||||
import org.springframework.statemachine.support.LifecycleObjectSupport;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link Trigger} capable of firing on a
|
||||
* static periods.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class TimerTrigger<S, E> extends LifecycleObjectSupport implements Trigger<S, E> {
|
||||
|
||||
private final CompositeTriggerListener triggerListener = new CompositeTriggerListener();
|
||||
|
||||
private final long period;
|
||||
|
||||
private final int count;
|
||||
private volatile ScheduledFuture<?> scheduled;
|
||||
|
||||
/**
|
||||
* Instantiates a new timer trigger.
|
||||
*
|
||||
* @param period the period in milliseconds
|
||||
*/
|
||||
public TimerTrigger(long period) {
|
||||
this(period, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new timer trigger.
|
||||
*
|
||||
* @param period the period
|
||||
* @param count the count
|
||||
*/
|
||||
public TimerTrigger(long period, int count) {
|
||||
this.period = period;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,25 +74,50 @@ public class TimerTrigger<S, E> extends LifecycleObjectSupport implements Trigge
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
scheduled = getTaskScheduler().scheduleAtFixedRate(new Runnable() {
|
||||
if (count > 0) {
|
||||
return;
|
||||
}
|
||||
schedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arm() {
|
||||
if (scheduled != null) {
|
||||
return;
|
||||
}
|
||||
schedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disarm() {
|
||||
if (count > 0) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void schedule() {
|
||||
scheduled = getTaskScheduler().schedule(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
notifyTriggered();
|
||||
}
|
||||
}, period);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
if (scheduled != null) {
|
||||
scheduled.cancel(true);
|
||||
}
|
||||
scheduled = null;
|
||||
}, new CountTrigger(count, period, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
private void notifyTriggered() {
|
||||
triggerListener.triggered();
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
if (scheduled != null) {
|
||||
scheduled.cancel(true);
|
||||
}
|
||||
scheduled = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2016 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.
|
||||
@@ -51,4 +51,15 @@ public interface Trigger<S,E> {
|
||||
*/
|
||||
E getEvent();
|
||||
|
||||
/**
|
||||
* Arm a trigger. After trigger has been armed a {@link TriggerListener}
|
||||
* may receive events.
|
||||
*/
|
||||
void arm();
|
||||
|
||||
/**
|
||||
* Disarm a trigger. After trigger has been disarmed a {@link TriggerListener}
|
||||
* will not receive events.
|
||||
*/
|
||||
void disarm();
|
||||
}
|
||||
|
||||
@@ -18,8 +18,11 @@ package org.springframework.statemachine.trigger;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -30,6 +33,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.TestUtils;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
@@ -119,6 +123,55 @@ public class TimerTriggerTests extends AbstractStateMachineTests {
|
||||
}
|
||||
}
|
||||
|
||||
private class TestTriggerListener implements TriggerListener {
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void triggered() {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testTimerDelayFireOnlyOnState() throws Exception {
|
||||
context.register(BaseConfig.class, Config4.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates, TestEvents> machine = context.getBean(StateMachine.class);
|
||||
TestTimerAction action = context.getBean("testTimerAction", TestTimerAction.class);
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
|
||||
TimerTrigger<?, ?> trigger = null;
|
||||
Map<Trigger<?, ?>, Transition<?, ?>> triggerToTransitionMap = TestUtils.readField("triggerToTransitionMap", machine);
|
||||
for (Entry<Trigger<?, ?>, Transition<?, ?>> entry : triggerToTransitionMap.entrySet()) {
|
||||
if (entry.getKey() instanceof TimerTrigger) {
|
||||
trigger = (TimerTrigger<?, ?>) entry.getKey();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
assertThat(trigger, notNullValue());
|
||||
TestTriggerListener tlistener = new TestTriggerListener();
|
||||
trigger.addTriggerListener(tlistener);
|
||||
|
||||
machine.start();
|
||||
assertThat(listener.stateMachineStartedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S1));
|
||||
|
||||
assertThat(tlistener.latch.await(2, TimeUnit.SECONDS), is(false));
|
||||
|
||||
listener.reset(1);
|
||||
machine.sendEvent(TestEvents.E1);
|
||||
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.stateChangedCount, is(1));
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2));
|
||||
|
||||
assertThat(action.latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
action.reset(1);
|
||||
assertThat(action.latch.await(2, TimeUnit.SECONDS), is(false));
|
||||
}
|
||||
|
||||
static class Config1 {
|
||||
|
||||
@Bean
|
||||
@@ -211,6 +264,39 @@ public class TimerTriggerTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config4 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S2)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withInternal()
|
||||
.source(TestStates.S2)
|
||||
.action(testTimerAction())
|
||||
.timerOnce(1000);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestTimerAction testTimerAction() {
|
||||
return new TestTimerAction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestTimerAction implements Action<TestStates, TestEvents> {
|
||||
|
||||
int count = 0;
|
||||
@@ -222,6 +308,10 @@ public class TimerTriggerTests extends AbstractStateMachineTests {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
void reset(int a) {
|
||||
latch = new CountDownLatch(a);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestTimerAction2 implements Action<String, String> {
|
||||
|
||||
@@ -182,7 +182,7 @@ public class CdPlayerTests {
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
|
||||
listener.reset(0, 0, 0, 2);
|
||||
assertThat(listener.transitionLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.transitionLatch.await(2100, TimeUnit.MILLISECONDS), is(true));
|
||||
assertThat(listener.transitionCount, is(2));
|
||||
assertLcdStatusNotContains("00:02");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user