Conflicting transitions cause undefined order behaviour
- Add new TransitionConflightPolicy and allow to configure it via an adapter interfaces. - For now, use it in TransitionComparator which sorts transitions based on source states. This gives PARENT and CHILD sorting and allows to choose conflicting transitions either from parent or child. - Add new sync thread used in submachines so that one thread is used in a whole call stach per machine(regions still get their own as well as a root machine). - Fixes #456
This commit is contained in:
@@ -159,6 +159,8 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public StateMachine<S, E> getStateMachine(UUID uuid, String machineId) {
|
||||
ArrayList<StateMachine<S, E>> machines = new ArrayList<>();
|
||||
|
||||
StateMachineModel<S, E> stateMachineModel = resolveStateMachineModel(machineId);
|
||||
if (stateMachineModel.getConfigurationData().isVerifierEnabled()) {
|
||||
StateMachineModelVerifier<S, E> verifier = stateMachineModel.getConfigurationData().getVerifier();
|
||||
@@ -221,6 +223,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel),
|
||||
resolveTaskScheduler(stateMachineModel), machineId, null, stateMachineModel);
|
||||
regionStack.push(new MachineStackItem<S, E>(machine));
|
||||
machines.add(machine);
|
||||
}
|
||||
|
||||
Collection<Region<S, E>> regions = new ArrayList<Region<S, E>>();
|
||||
@@ -244,11 +247,13 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
machineId != null ? machineId : stateMachineModel.getConfigurationData().getMachineId(),
|
||||
uuid, stateMachineModel);
|
||||
machine = m;
|
||||
machines.add(m);
|
||||
}
|
||||
} else {
|
||||
machine = buildMachine(machineMap, stateMap, holderList, stateDatas, transitionsData, resolveBeanFactory(stateMachineModel), contextEvents,
|
||||
defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel), resolveTaskScheduler(stateMachineModel),
|
||||
machineId, uuid, stateMachineModel);
|
||||
machines.add(machine);
|
||||
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
|
||||
machineMap.put(peek.getParent(), machine);
|
||||
}
|
||||
@@ -289,6 +294,35 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
});
|
||||
}
|
||||
|
||||
// set parent machines for each built machine
|
||||
for (Entry<Object, StateMachine<S, E>> mme : machineMap.entrySet()) {
|
||||
StateMachine<S, E> m = null;
|
||||
if (mme.getKey() != null) {
|
||||
Object sParent = null;
|
||||
for (StateData<S, E> sd : stateMachineModel.getStatesData().getStateData()) {
|
||||
if (ObjectUtils.nullSafeEquals(sd.getState(), mme.getKey())) {
|
||||
sParent = sd.getParent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
m = machineMap.get(sParent);
|
||||
}
|
||||
final StateMachine<S, E> mm = m;
|
||||
mme.getValue().getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S ,E>>(){
|
||||
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
function.setParentMachine(mm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// init built machines
|
||||
for (StateMachine<S, E> m : machines) {
|
||||
((LifecycleObjectSupport)m).afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
// TODO: should error out if sec is enabled but spring-security is not in cp
|
||||
if (stateMachineModel.getConfigurationData().isSecurityEnabled()) {
|
||||
final StateMachineSecurityInterceptor<S, E> securityInterceptor = new StateMachineSecurityInterceptor<S, E>(
|
||||
@@ -338,28 +372,6 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
holderItem.value.setState(stateMap.get(holderItem.key));
|
||||
}
|
||||
|
||||
// set parent machines for each built machine
|
||||
for (Entry<Object, StateMachine<S, E>> mme : machineMap.entrySet()) {
|
||||
StateMachine<S, E> m = null;
|
||||
if (mme.getKey() != null) {
|
||||
Object sParent = null;
|
||||
for (StateData<S, E> sd : stateMachineModel.getStatesData().getStateData()) {
|
||||
if (ObjectUtils.nullSafeEquals(sd.getState(), mme.getKey())) {
|
||||
sParent = sd.getParent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
m = machineMap.get(sParent);
|
||||
}
|
||||
final StateMachine<S, E> mm = m;
|
||||
mme.getValue().getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S ,E>>(){
|
||||
|
||||
@Override
|
||||
public void apply(StateMachineAccess<S, E> function) {
|
||||
function.setParentMachine(mm);
|
||||
}
|
||||
});
|
||||
}
|
||||
return delegateAutoStartup(machine);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 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.
|
||||
@@ -76,6 +76,7 @@ public class ObjectStateMachineFactory<S, E> extends AbstractStateMachineFactory
|
||||
extendedState, uuid);
|
||||
machine.setId(machineId);
|
||||
machine.setHistoryState(historyState);
|
||||
machine.setTransitionConflightPolicy(stateMachineModel.getConfigurationData().getTransitionConflightPolicy());
|
||||
if (contextEventsEnabled != null) {
|
||||
machine.setContextEventsEnabled(contextEventsEnabled);
|
||||
}
|
||||
@@ -91,7 +92,6 @@ public class ObjectStateMachineFactory<S, E> extends AbstractStateMachineFactory
|
||||
if (machine instanceof BeanNameAware) {
|
||||
((BeanNameAware)machine).setBeanName(beanName);
|
||||
}
|
||||
machine.afterPropertiesSet();
|
||||
return machine;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2017 the original author or authors.
|
||||
* Copyright 2017 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.
|
||||
@@ -46,6 +46,7 @@ import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
|
||||
/**
|
||||
* {@link AnnotationBuilder} for {@link StatesData}.
|
||||
@@ -64,6 +65,7 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
private TaskExecutor taskExecutor;
|
||||
private TaskScheduler taskScheculer;
|
||||
private boolean autoStart = false;
|
||||
private TransitionConflightPolicy transitionConflightPolicy;
|
||||
private StateMachineEnsemble<S, E> ensemble;
|
||||
private final List<StateMachineListener<S, E>> listeners = new ArrayList<StateMachineListener<S, E>>();
|
||||
private boolean securityEnabled = false;
|
||||
@@ -145,7 +147,7 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
}
|
||||
return new ConfigurationData<S, E>(beanFactory, taskExecutor, taskScheculer, autoStart, ensemble, listeners,
|
||||
securityEnabled, transitionSecurityAccessDecisionManager, eventSecurityAccessDecisionManager, eventSecurityRule,
|
||||
transitionSecurityRule, verifierEnabled, verifier, machineId, stateMachineMonitor, interceptorsCopy);
|
||||
transitionSecurityRule, verifierEnabled, verifier, machineId, stateMachineMonitor, interceptorsCopy, transitionConflightPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,4 +294,13 @@ public class StateMachineConfigurationBuilder<S, E>
|
||||
public void setStateMachineRuntimePersister(StateMachineRuntimePersister<S, E, ?> persister) {
|
||||
this.persister = persister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transition conflight policy.
|
||||
*
|
||||
* @param transitionConflightPolicy the new transition conflight policy
|
||||
*/
|
||||
public void setTransitionConflightPolicy(TransitionConflightPolicy transitionConflightPolicy) {
|
||||
this.transitionConflightPolicy = transitionConflightPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2017 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.
|
||||
@@ -22,6 +22,7 @@ import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
|
||||
/**
|
||||
* Base {@code ConfigConfigurer} interface for configuring generic config.
|
||||
@@ -86,4 +87,11 @@ public interface ConfigurationConfigurer<S, E> extends
|
||||
*/
|
||||
ConfigurationConfigurer<S, E> listener(StateMachineListener<S, E> listener);
|
||||
|
||||
/**
|
||||
* Speficy a {@link TransitionConflightPolicy}.
|
||||
*
|
||||
* @param transitionConflightPolicy the transition conflight policy
|
||||
* @return the configuration configurer
|
||||
*/
|
||||
ConfigurationConfigurer<S, E> transitionConflightPolicy(TransitionConflightPolicy transitionConflightPolicy);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 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.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.statemachine.config.builders.StateMachineConfiguratio
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link ConfigurationConfigurer}.
|
||||
@@ -44,6 +45,7 @@ public class DefaultConfigurationConfigurer<S, E>
|
||||
private TaskExecutor taskExecutor;
|
||||
private TaskScheduler taskScheculer;
|
||||
private boolean autoStart = false;
|
||||
private TransitionConflightPolicy transitionConflightPolicy;
|
||||
private final List<StateMachineListener<S, E>> listeners = new ArrayList<StateMachineListener<S, E>>();
|
||||
|
||||
@Override
|
||||
@@ -54,6 +56,7 @@ public class DefaultConfigurationConfigurer<S, E>
|
||||
builder.setTaskScheculer(taskScheculer);
|
||||
builder.setAutoStart(autoStart);
|
||||
builder.setStateMachineListeners(listeners);
|
||||
builder.setTransitionConflightPolicy(transitionConflightPolicy);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,4 +95,9 @@ public class DefaultConfigurationConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationConfigurer<S, E> transitionConflightPolicy(TransitionConflightPolicy transitionConflightPolicy) {
|
||||
this.transitionConflightPolicy = transitionConflightPolicy;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.security.SecurityRule;
|
||||
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
|
||||
/**
|
||||
* Configuration object used to keep things together in {@link StateMachineConfigurationBuilder}.
|
||||
@@ -48,6 +49,7 @@ public class ConfigurationData<S, E> {
|
||||
private final TaskExecutor taskExecutor;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final boolean autoStart;
|
||||
private final TransitionConflightPolicy transitionConflightPolicy;
|
||||
private final StateMachineEnsemble<S, E> ensemble;
|
||||
private final List<StateMachineListener<S, E>> listeners;
|
||||
private final boolean securityEnabled;
|
||||
@@ -95,6 +97,39 @@ public class ConfigurationData<S, E> {
|
||||
SecurityRule eventSecurityRule, SecurityRule transitionSecurityRule, boolean verifierEnabled,
|
||||
StateMachineModelVerifier<S, E> verifier, String machineId, StateMachineMonitor<S, E> stateMachineMonitor,
|
||||
List<StateMachineInterceptor<S, E>> interceptors) {
|
||||
this(beanFactory, taskExecutor, taskScheduler, autoStart, ensemble, listeners, securityEnabled,
|
||||
transitionSecurityAccessDecisionManager, eventSecurityAccessDecisionManager, eventSecurityRule, transitionSecurityRule,
|
||||
verifierEnabled, verifier, machineId, stateMachineMonitor, interceptors, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new state machine configuration config data.
|
||||
*
|
||||
* @param beanFactory the bean factory
|
||||
* @param taskExecutor the task executor
|
||||
* @param taskScheduler the task scheduler
|
||||
* @param autoStart the autostart flag
|
||||
* @param ensemble the state machine ensemble
|
||||
* @param listeners the state machine listeners
|
||||
* @param securityEnabled the security enabled flag
|
||||
* @param transitionSecurityAccessDecisionManager the transition security access decision manager
|
||||
* @param eventSecurityAccessDecisionManager the event security access decision manager
|
||||
* @param eventSecurityRule the event security rule
|
||||
* @param transitionSecurityRule the transition security rule
|
||||
* @param verifierEnabled the verifier enabled flag
|
||||
* @param verifier the state machine model verifier
|
||||
* @param machineId the machine id
|
||||
* @param stateMachineMonitor the state machine monitor
|
||||
* @param interceptors the state machine interceptors.
|
||||
* @param transitionConflightPolicy the transition conflight policy
|
||||
*/
|
||||
public ConfigurationData(BeanFactory beanFactory, TaskExecutor taskExecutor,
|
||||
TaskScheduler taskScheduler, boolean autoStart, StateMachineEnsemble<S, E> ensemble,
|
||||
List<StateMachineListener<S, E>> listeners, boolean securityEnabled,
|
||||
AccessDecisionManager transitionSecurityAccessDecisionManager, AccessDecisionManager eventSecurityAccessDecisionManager,
|
||||
SecurityRule eventSecurityRule, SecurityRule transitionSecurityRule, boolean verifierEnabled,
|
||||
StateMachineModelVerifier<S, E> verifier, String machineId, StateMachineMonitor<S, E> stateMachineMonitor,
|
||||
List<StateMachineInterceptor<S, E>> interceptors, TransitionConflightPolicy transitionConflightPolicy) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.taskExecutor = taskExecutor;
|
||||
this.taskScheduler = taskScheduler;
|
||||
@@ -111,6 +146,7 @@ public class ConfigurationData<S, E> {
|
||||
this.machineId = machineId;
|
||||
this.stateMachineMonitor = stateMachineMonitor;
|
||||
this.interceptors = interceptors;
|
||||
this.transitionConflightPolicy = transitionConflightPolicy;
|
||||
}
|
||||
|
||||
public String getMachineId() {
|
||||
@@ -251,4 +287,13 @@ public class ConfigurationData<S, E> {
|
||||
public List<StateMachineInterceptor<S, E>> getStateMachineInterceptors() {
|
||||
return interceptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transition conflight policy.
|
||||
*
|
||||
* @return the transition conflight policy
|
||||
*/
|
||||
public TransitionConflightPolicy getTransitionConflightPolicy() {
|
||||
return transitionConflightPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2017 the original author or authors.
|
||||
* Copyright 2017 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.
|
||||
@@ -21,6 +21,7 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
@@ -51,6 +52,7 @@ import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.support.StateMachineExecutor.StateMachineExecutorTransit;
|
||||
import org.springframework.statemachine.transition.InitialTransition;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
import org.springframework.statemachine.trigger.DefaultTriggerContext;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
@@ -91,6 +93,8 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
|
||||
private ExtendedState extendedState;
|
||||
|
||||
private TransitionConflightPolicy transitionConflightPolicy;
|
||||
|
||||
private volatile State<S,E> currentState;
|
||||
|
||||
// using this to log last state when machine stops, as
|
||||
@@ -274,12 +278,19 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
|
||||
DefaultStateMachineExecutor<S, E> executor = new DefaultStateMachineExecutor<S, E>(this, getRelayStateMachine(), transitions,
|
||||
triggerToTransitionMap, triggerlessTransitions, initialTransition, initialEvent);
|
||||
triggerToTransitionMap, triggerlessTransitions, initialTransition, initialEvent, transitionConflightPolicy);
|
||||
if (getBeanFactory() != null) {
|
||||
executor.setBeanFactory(getBeanFactory());
|
||||
}
|
||||
if (getTaskExecutor() != null){
|
||||
executor.setTaskExecutor(getTaskExecutor());
|
||||
// parent machine is set when we're on substates(not regions)
|
||||
// so then force sync executor which makes things a bit more reliable
|
||||
// as state execution should anyway get synched with plain substates.
|
||||
if(parentMachine != null) {
|
||||
executor.setTaskExecutor(new SyncTaskExecutor());
|
||||
} else {
|
||||
executor.setTaskExecutor(getTaskExecutor());
|
||||
}
|
||||
}
|
||||
executor.afterPropertiesSet();
|
||||
executor.setStateMachineExecutorTransit(new StateMachineExecutorTransit<S, E>() {
|
||||
@@ -562,6 +573,15 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
forwardedInitialEvent = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transition conflight policy.
|
||||
*
|
||||
* @param transitionConflightPolicy the new transition conflight policy
|
||||
*/
|
||||
public void setTransitionConflightPolicy(TransitionConflightPolicy transitionConflightPolicy) {
|
||||
this.transitionConflightPolicy = transitionConflightPolicy;
|
||||
}
|
||||
|
||||
private boolean sendEventInternal(Message<E> event) {
|
||||
if (hasStateMachineError()) {
|
||||
// TODO: should we throw exception?
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.statemachine.state.JoinPseudoState;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
import org.springframework.statemachine.trigger.DefaultTriggerContext;
|
||||
import org.springframework.statemachine.trigger.TimerTrigger;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
@@ -101,6 +102,8 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
private final TransitionComparator<S, E> transitionComparator;;
|
||||
|
||||
/**
|
||||
* Instantiates a new default state machine executor.
|
||||
*
|
||||
@@ -111,10 +114,12 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
* @param triggerlessTransitions the triggerless transitions
|
||||
* @param initialTransition the initial transition
|
||||
* @param initialEvent the initial event
|
||||
* @param transitionConflightPolicy the transition conflight policy
|
||||
*/
|
||||
public DefaultStateMachineExecutor(StateMachine<S, E> stateMachine, StateMachine<S, E> relayStateMachine,
|
||||
Collection<Transition<S, E>> transitions, Map<Trigger<S, E>, Transition<S, E>> triggerToTransitionMap,
|
||||
List<Transition<S, E>> triggerlessTransitions, Transition<S, E> initialTransition, Message<E> initialEvent) {
|
||||
List<Transition<S, E>> triggerlessTransitions, Transition<S, E> initialTransition, Message<E> initialEvent,
|
||||
TransitionConflightPolicy transitionConflightPolicy) {
|
||||
this.stateMachine = stateMachine;
|
||||
this.relayStateMachine = relayStateMachine;
|
||||
this.triggerToTransitionMap = triggerToTransitionMap;
|
||||
@@ -122,6 +127,9 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
this.transitions = transitions;
|
||||
this.initialTransition = initialTransition;
|
||||
this.initialEvent = initialEvent;
|
||||
this.transitionComparator = new TransitionComparator<S, E>(transitionConflightPolicy);
|
||||
// anonymous transitions are fixed, sort those now
|
||||
this.triggerlessTransitions.sort(transitionComparator);
|
||||
registerTriggerListener();
|
||||
}
|
||||
|
||||
@@ -417,7 +425,8 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
||||
trans.add(triggerToTransitionMap.get(queueItem.trigger));
|
||||
}
|
||||
|
||||
// go through candidates and transit max one
|
||||
// go through candidates and transit max one, sort before handling
|
||||
trans.sort(transitionComparator);
|
||||
handleTriggerTrans(trans, queuedMessage);
|
||||
}
|
||||
if (stateMachine.getState() != null) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2017 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.support;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
|
||||
/**
|
||||
* {@link Comparator} for {@link Transition}s. This comparator tries to compare
|
||||
* transitions simply checking
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
class TransitionComparator<S, E> implements Comparator<Transition<S, E>> {
|
||||
|
||||
private final static Log log = LogFactory.getLog(TransitionComparator.class);
|
||||
private final TransitionConflightPolicy transitionConflightPolicy;
|
||||
|
||||
/**
|
||||
* Instantiates a new transition comparator.
|
||||
*
|
||||
* @param transitionConflightPolicy the transition conflight policy
|
||||
*/
|
||||
public TransitionComparator(TransitionConflightPolicy transitionConflightPolicy) {
|
||||
this.transitionConflightPolicy = transitionConflightPolicy == null ? TransitionConflightPolicy.CHILD : transitionConflightPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Transition<S, E> left, Transition<S, E> right) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Compare left='" + left + "' right='" + right +"'");
|
||||
}
|
||||
if (left == right) {
|
||||
return 0;
|
||||
} else {
|
||||
boolean substate = StateMachineUtils.isSubstate(left.getSource(), right.getSource());
|
||||
if (transitionConflightPolicy == TransitionConflightPolicy.CHILD) {
|
||||
return substate ? 1 : -1;
|
||||
} else {
|
||||
return substate ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TransitionComparator [transitionConflightPolicy=" + transitionConflightPolicy + "]";
|
||||
}
|
||||
}
|
||||
@@ -168,4 +168,9 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AbstractTransition [source=" + source + ", target=" + target + ", kind=" + kind + ", guard=" + guard + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
/**
|
||||
* Enumerations for possible transition conflight policies.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public enum TransitionConflightPolicy {
|
||||
|
||||
/**
|
||||
* Choose transition from a child.
|
||||
*/
|
||||
CHILD,
|
||||
|
||||
/**
|
||||
* Choose transition from a parent.
|
||||
*/
|
||||
PARENT;
|
||||
}
|
||||
@@ -89,7 +89,8 @@ public class DefaultStateMachineExecutorTests {
|
||||
triggerToTransitionMap,
|
||||
triggerlessTransitions,
|
||||
initialTransition,
|
||||
initialEvent);
|
||||
initialEvent,
|
||||
null);
|
||||
|
||||
executor.setTaskExecutor(taskExecutor);
|
||||
|
||||
@@ -162,7 +163,8 @@ public class DefaultStateMachineExecutorTests {
|
||||
triggerToTransitionMap,
|
||||
triggerlessTransitions,
|
||||
initialTransition,
|
||||
initialEvent);
|
||||
initialEvent,
|
||||
null);
|
||||
|
||||
executor.setTaskExecutor(taskExecutor);
|
||||
|
||||
@@ -237,7 +239,8 @@ public class DefaultStateMachineExecutorTests {
|
||||
triggerToTransitionMap,
|
||||
triggerlessTransitions,
|
||||
initialTransition,
|
||||
initialEvent);
|
||||
initialEvent,
|
||||
null);
|
||||
|
||||
executor.setTaskExecutor(taskExecutor);
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2017 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.support;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests.TestEvents;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests.TestStates;
|
||||
import org.springframework.statemachine.ObjectStateMachine;
|
||||
import org.springframework.statemachine.state.DefaultPseudoState;
|
||||
import org.springframework.statemachine.state.EnumState;
|
||||
import org.springframework.statemachine.state.PseudoState;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.state.StateMachineState;
|
||||
import org.springframework.statemachine.transition.DefaultExternalTransition;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionConflightPolicy;
|
||||
import org.springframework.statemachine.trigger.EventTrigger;
|
||||
|
||||
/**
|
||||
* Tests for {@link TransitionComparator}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class TransitionComparatorTests {
|
||||
|
||||
@Test
|
||||
public void testCompareWithParentAndChild() {
|
||||
PseudoState<TestStates, TestEvents> pseudoState = new DefaultPseudoState<TestStates, TestEvents>(PseudoStateKind.INITIAL);
|
||||
|
||||
State<TestStates, TestEvents> stateS111 = new EnumState<TestStates, TestEvents>(TestStates.S111, null, null, null, pseudoState);
|
||||
|
||||
// submachine 11
|
||||
Collection<State<TestStates, TestEvents>> substates111 = new ArrayList<State<TestStates, TestEvents>>();
|
||||
substates111.add(stateS111);
|
||||
Collection<Transition<TestStates, TestEvents>> subtransitions111 = new ArrayList<Transition<TestStates, TestEvents>>();
|
||||
ObjectStateMachine<TestStates, TestEvents> submachine11 = new ObjectStateMachine<TestStates, TestEvents>(substates111,
|
||||
subtransitions111, stateS111);
|
||||
|
||||
// submachine 1
|
||||
StateMachineState<TestStates, TestEvents> stateS11 = new StateMachineState<TestStates, TestEvents>(TestStates.S11, submachine11,
|
||||
null, null, null, pseudoState);
|
||||
|
||||
Collection<State<TestStates, TestEvents>> substates11 = new ArrayList<State<TestStates, TestEvents>>();
|
||||
substates11.add(stateS11);
|
||||
Collection<Transition<TestStates, TestEvents>> subtransitions11 = new ArrayList<Transition<TestStates, TestEvents>>();
|
||||
ObjectStateMachine<TestStates, TestEvents> submachine1 = new ObjectStateMachine<TestStates, TestEvents>(substates11,
|
||||
subtransitions11, stateS11);
|
||||
|
||||
// machine
|
||||
StateMachineState<TestStates, TestEvents> stateS1 = new StateMachineState<TestStates, TestEvents>(TestStates.S1, submachine1, null,
|
||||
null, null, pseudoState);
|
||||
|
||||
DefaultExternalTransition<TestStates, TestEvents> transitionFromS111ToS1 = new DefaultExternalTransition<TestStates, TestEvents>(
|
||||
stateS111, stateS1, null, TestEvents.E1, null, new EventTrigger<TestStates, TestEvents>(TestEvents.E1));
|
||||
DefaultExternalTransition<TestStates, TestEvents> transitionFromS11ToS1 = new DefaultExternalTransition<TestStates, TestEvents>(
|
||||
stateS11, stateS1, null, TestEvents.E1, null, new EventTrigger<TestStates, TestEvents>(TestEvents.E1));
|
||||
|
||||
TransitionComparator<TestStates, TestEvents> comparator = new TransitionComparator<>(null);
|
||||
assertThat(comparator.compare(transitionFromS111ToS1, transitionFromS11ToS1), is(-1));
|
||||
assertThat(comparator.compare(transitionFromS11ToS1, transitionFromS111ToS1), is(1));
|
||||
assertThat(comparator.compare(transitionFromS111ToS1, transitionFromS111ToS1), is(0));
|
||||
assertThat(comparator.compare(transitionFromS11ToS1, transitionFromS11ToS1), is(0));
|
||||
|
||||
comparator = new TransitionComparator<>(TransitionConflightPolicy.CHILD);
|
||||
assertThat(comparator.compare(transitionFromS111ToS1, transitionFromS11ToS1), is(-1));
|
||||
assertThat(comparator.compare(transitionFromS11ToS1, transitionFromS111ToS1), is(1));
|
||||
assertThat(comparator.compare(transitionFromS111ToS1, transitionFromS111ToS1), is(0));
|
||||
assertThat(comparator.compare(transitionFromS11ToS1, transitionFromS11ToS1), is(0));
|
||||
|
||||
comparator = new TransitionComparator<>(TransitionConflightPolicy.PARENT);
|
||||
assertThat(comparator.compare(transitionFromS111ToS1, transitionFromS11ToS1), is(1));
|
||||
assertThat(comparator.compare(transitionFromS11ToS1, transitionFromS111ToS1), is(-1));
|
||||
assertThat(comparator.compare(transitionFromS111ToS1, transitionFromS111ToS1), is(0));
|
||||
assertThat(comparator.compare(transitionFromS11ToS1, transitionFromS11ToS1), is(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
* Copyright 2017 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.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.messaging.support.MessageBuilder;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests for transitions for cases where specific order is assumed.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class TransitionOrderTests extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent1() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config1.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
assertThat(listener.statesEntered, contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered, contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent2() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config2.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered, contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseChild1() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config3.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered, contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1012, TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent3() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config4.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered, contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent4() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config5.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered, contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1012));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent5() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config6.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered, contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent6() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config7.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered,
|
||||
contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1012, TestStates.S2011, TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent7() {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config8.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEntered,
|
||||
contains(TestStates.S1, TestStates.S10, TestStates.S1011, TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testAnonymousTransitionInConfigUseParent3Threading() throws InterruptedException {
|
||||
TestListener listener = new TestListener();
|
||||
context.register(Config4.class, StateMachineExecutorConfiguration.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
machine.addStateListener(listener);
|
||||
|
||||
machine.start();
|
||||
assertThat(listener.statesEnteredLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
listener.reset(1, 3);
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.statesEnteredLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.statesEntered, contains(TestStates.S10, TestStates.S1011, TestStates.S1));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.state(TestStates.S1012);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
// Config1 vs. Config2, next 2 transitions are defined in different order
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S1);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config2 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.state(TestStates.S1012);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
// Config1 vs. Config2, next 2 transitions are defined in different order
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config3 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.CHILD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.state(TestStates.S1012);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config4 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.stateEntry(TestStates.S1011, c -> c.getExtendedState().getVariables().put("error", true))
|
||||
.state(TestStates.S1012);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S1)
|
||||
.guard(c -> c.getExtendedState().getVariables().containsKey("error"))
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config5 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.state(TestStates.S1012);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S1)
|
||||
.guard(c -> c.getExtendedState().getVariables().containsKey("error"))
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config6 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.state(TestStates.S1012)
|
||||
.state(TestStates.S2011);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1012)
|
||||
.target(TestStates.S2011);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config7 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.CHILD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.state(TestStates.S1012)
|
||||
.state(TestStates.S2011);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1012)
|
||||
.target(TestStates.S2011);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config8 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.transitionConflightPolicy(TransitionConflightPolicy.PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S10)
|
||||
.junction(TestStates.SF)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S10)
|
||||
.initial(TestStates.S1011)
|
||||
.state(TestStates.S1012)
|
||||
.state(TestStates.S2011);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withJunction()
|
||||
.source(TestStates.SF)
|
||||
.last(TestStates.S1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S10)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S1012)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.SF)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S1012)
|
||||
.target(TestStates.S2011);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class StateMachineExecutorConfiguration {
|
||||
|
||||
@Bean(name = StateMachineSystemConstants.TASK_EXECUTOR_BEAN_NAME)
|
||||
public TaskExecutor stateMachineTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class TestListener extends StateMachineListenerAdapter<TestStates, TestEvents> {
|
||||
|
||||
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||
volatile int stateChangedCount = 0;
|
||||
volatile CountDownLatch statesEnteredLatch = new CountDownLatch(1);
|
||||
final ArrayList<TestStates> statesEntered = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<TestStates, TestEvents> from, State<TestStates, TestEvents> to) {
|
||||
stateChangedCount++;
|
||||
stateChangedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateEntered(State<TestStates, TestEvents> state) {
|
||||
statesEntered.add(state.getId());
|
||||
statesEnteredLatch.countDown();
|
||||
}
|
||||
|
||||
public void reset(int c1) {
|
||||
stateChangedLatch = new CountDownLatch(c1);
|
||||
stateChangedCount = 0;
|
||||
}
|
||||
|
||||
public void reset(int c1, int c2) {
|
||||
stateChangedLatch = new CountDownLatch(c1);
|
||||
stateChangedCount = 0;
|
||||
statesEnteredLatch = new CountDownLatch(c2);
|
||||
statesEntered.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user