/*
* 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.
* 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.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.Message;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.access.StateMachineFunction;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
import org.springframework.statemachine.config.model.HistoryData;
import org.springframework.statemachine.config.model.JunctionData;
import org.springframework.statemachine.config.model.StateData;
import org.springframework.statemachine.config.model.StateMachineModel;
import org.springframework.statemachine.config.model.StateMachineModelFactory;
import org.springframework.statemachine.config.model.TransitionData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.statemachine.config.model.verifier.CompositeStateMachineModelVerifier;
import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier;
import org.springframework.statemachine.ensemble.DistributedStateMachine;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.monitor.StateMachineMonitor;
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;
import org.springframework.statemachine.state.EntryPseudoState;
import org.springframework.statemachine.state.ExitPseudoState;
import org.springframework.statemachine.state.ForkPseudoState;
import org.springframework.statemachine.state.HistoryPseudoState;
import org.springframework.statemachine.state.JoinPseudoState;
import org.springframework.statemachine.state.JoinPseudoState.JoinStateData;
import org.springframework.statemachine.state.JunctionPseudoState;
import org.springframework.statemachine.state.JunctionPseudoState.JunctionStateData;
import org.springframework.statemachine.state.PseudoState;
import org.springframework.statemachine.state.PseudoStateKind;
import org.springframework.statemachine.state.RegionState;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.state.StateHolder;
import org.springframework.statemachine.state.StateMachineState;
import org.springframework.statemachine.support.DefaultExtendedState;
import org.springframework.statemachine.support.LifecycleObjectSupport;
import org.springframework.statemachine.support.tree.Tree;
import org.springframework.statemachine.support.tree.Tree.Node;
import org.springframework.statemachine.support.tree.TreeTraverser;
import org.springframework.statemachine.transition.DefaultExternalTransition;
import org.springframework.statemachine.transition.DefaultInternalTransition;
import org.springframework.statemachine.transition.DefaultLocalTransition;
import org.springframework.statemachine.transition.InitialTransition;
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;
/**
* Base {@link StateMachineFactory} implementation building {@link StateMachine}s.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectSupport implements
StateMachineFactory<S, E>, BeanNameAware {
private final Log log = LogFactory.getLog(AbstractStateMachineFactory.class);
private final StateMachineModel<S, E> defaultStateMachineModel;
private final StateMachineModelFactory<S, E> stateMachineModelFactory;
private Boolean contextEvents;
private boolean handleAutostartup = false;
private String beanName;
private StateMachineMonitor<S, E> defaultStateMachineMonitor;
/**
* Instantiates a new abstract state machine factory.
*
* @param defaultStateMachineModel the default state machine model
* @param stateMachineModelFactory the state machine model factory
*/
public AbstractStateMachineFactory(StateMachineModel<S, E> defaultStateMachineModel, StateMachineModelFactory<S, E> stateMachineModelFactory) {
this.stateMachineModelFactory = stateMachineModelFactory;
this.defaultStateMachineModel = defaultStateMachineModel;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public StateMachine<S, E> getStateMachine() {
return getStateMachine(null, null);
}
@Override
public StateMachine<S, E> getStateMachine(String machineId) {
return getStateMachine(null, machineId);
}
@Override
public StateMachine<S, E> getStateMachine(UUID uuid) {
return getStateMachine(uuid, null);
}
/**
* Main constructor that create a {@link StateMachine}.
*
* @param uuid for internal usage. Can be null, in that case a random one will be generated.
* @param machineId represent a user Id, up to you to set what you want.
* @return a {@link StateMachine}
*/
@SuppressWarnings("unchecked")
public StateMachine<S, E> getStateMachine(UUID uuid, String machineId) {
StateMachineModel<S, E> stateMachineModel = resolveStateMachineModel(machineId);
if (stateMachineModel.getConfigurationData().isVerifierEnabled()) {
StateMachineModelVerifier<S, E> verifier = stateMachineModel.getConfigurationData().getVerifier();
if (verifier == null) {
verifier = new CompositeStateMachineModelVerifier<S, E>();
}
verifier.verify(stateMachineModel);
}
// shared
DefaultExtendedState defaultExtendedState = new DefaultExtendedState();
StateMachine<S, E> machine = null;
// we store mappings from state id's to states which gets
// created during the process. This is needed for transitions to
// find a correct mappings because they use state id's, not actual
// states.
final Map<S, State<S, E>> stateMap = new HashMap<S, State<S, E>>();
Stack<MachineStackItem<S, E>> regionStack = new Stack<MachineStackItem<S, E>>();
Stack<StateData<S, E>> stateStack = new Stack<StateData<S, E>>();
Map<Object, StateMachine<S, E>> machineMap = new HashMap<Object, StateMachine<S,E>>();
Map<S, StateHolder<S, E>> holderMap = new HashMap<S, StateHolder<S, E>>();
Iterator<Node<StateData<S, E>>> iterator = buildStateDataIterator(stateMachineModel);
while (iterator.hasNext()) {
Node<StateData<S, E>> node = iterator.next();
StateData<S, E> stateData = node.getData();
StateData<S, E> peek = stateStack.isEmpty() ? null : stateStack.peek();
// simply push and continue
if (stateStack.isEmpty()) {
stateStack.push(stateData);
continue;
}
boolean stackContainsSameParent = false;
Iterator<StateData<S, E>> ii = stateStack.iterator();
while (ii.hasNext()) {
StateData<S, E> sd = ii.next();
if (stateData != null && ObjectUtils.nullSafeEquals(stateData.getState(), sd.getParent())) {
stackContainsSameParent = true;
break;
}
}
if (stateData != null && !stackContainsSameParent) {
stateStack.push(stateData);
continue;
}
Collection<StateData<S, E>> stateDatas = popSameParents(stateStack);
int initialCount = getInitialCount(stateDatas);
Collection<Collection<StateData<S, E>>> regionsStateDatas = splitIntoRegions(stateDatas);
Collection<TransitionData<S, E>> transitionsData = getTransitionData(iterator.hasNext(), stateDatas, stateMachineModel);
if (initialCount > 1) {
for (Collection<StateData<S, E>> regionStateDatas : regionsStateDatas) {
machine = buildMachine(machineMap, stateMap, holderMap, regionStateDatas, transitionsData, resolveBeanFactory(stateMachineModel),
contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel),
resolveTaskScheduler(stateMachineModel), machineId, null, stateMachineModel);
regionStack.push(new MachineStackItem<S, E>(machine));
}
Collection<Region<S, E>> regions = new ArrayList<Region<S, E>>();
while (!regionStack.isEmpty()) {
MachineStackItem<S, E> pop = regionStack.pop();
regions.add(pop.machine);
}
S parent = (S)peek.getParent();
RegionState<S, E> rstate = buildRegionStateInternal(parent, regions, null, stateData != null ? stateData.getEntryActions() : null,
stateData != null ? stateData.getExitActions() : null, new DefaultPseudoState<S, E>(PseudoStateKind.INITIAL));
if (stateData != null) {
stateMap.put(stateData.getState(), rstate);
} else {
// TODO: don't like that we create a last machine here
Collection<State<S, E>> states = new ArrayList<State<S, E>>();
states.add(rstate);
Transition<S, E> initialTransition = new InitialTransition<S, E>(rstate);
StateMachine<S, E> m = buildStateMachineInternal(states, new ArrayList<Transition<S, E>>(), rstate, initialTransition,
null, defaultExtendedState, null, contextEvents, resolveBeanFactory(stateMachineModel), resolveTaskExecutor(stateMachineModel),
resolveTaskScheduler(stateMachineModel), beanName,
machineId != null ? machineId : stateMachineModel.getConfigurationData().getMachineId(),
uuid, stateMachineModel);
machine = m;
}
} else {
machine = buildMachine(machineMap, stateMap, holderMap, stateDatas, transitionsData, resolveBeanFactory(stateMachineModel), contextEvents,
defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel), resolveTaskScheduler(stateMachineModel),
machineId, uuid, stateMachineModel);
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
machineMap.put(peek.getParent(), machine);
}
}
stateStack.push(stateData);
}
// setup autostart for top-level machine
if (machine instanceof LifecycleObjectSupport) {
((LifecycleObjectSupport)machine).setAutoStartup(stateMachineModel.getConfigurationData().isAutoStart());
}
// set top-level machine as relay
final StateMachine<S, E> fmachine = machine;
fmachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {
@Override
public void apply(StateMachineAccess<S, E> function) {
function.setRelay(fmachine);
}
});
// add monitoring hooks
final StateMachineMonitor<S, E> stateMachineMonitor = stateMachineModel.getConfigurationData().getStateMachineMonitor();
if (stateMachineMonitor != null || defaultStateMachineMonitor != null) {
fmachine.getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S ,E>>() {
@Override
public void apply(StateMachineAccess<S, E> function) {
if (defaultStateMachineMonitor != null) {
function.addStateMachineMonitor(defaultStateMachineMonitor);
}
if (stateMachineMonitor != null) {
function.addStateMachineMonitor(stateMachineMonitor);
}
}
});
}
// 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>(
stateMachineModel.getConfigurationData().getTransitionSecurityAccessDecisionManager(),
stateMachineModel.getConfigurationData().getEventSecurityAccessDecisionManager(),
stateMachineModel.getConfigurationData().getEventSecurityRule());
log.info("Adding security interceptor " + securityInterceptor);
fmachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {
@Override
public void apply(StateMachineAccess<S, E> function) {
function.addStateMachineInterceptor(securityInterceptor);
}
});
}
// setup distributed state machine if needed.
// we wrap previously build machine with a distributed
// state machine and set it to use given ensemble.
if (stateMachineModel.getConfigurationData().getStateMachineEnsemble() != null) {
DistributedStateMachine<S, E> distributedStateMachine = new DistributedStateMachine<S, E>(
stateMachineModel.getConfigurationData().getStateMachineEnsemble(), machine);
distributedStateMachine.setAutoStartup(stateMachineModel.getConfigurationData().isAutoStart());
distributedStateMachine.afterPropertiesSet();
machine = distributedStateMachine;
}
for (StateMachineListener<S, E> listener : stateMachineModel.getConfigurationData().getStateMachineListeners()) {
machine.addStateListener(listener);
}
// go through holders and fix state references which
// were not known at a time holder was created
for (Entry<S, StateHolder<S, E>> holder : holderMap.entrySet()) {
holder.getValue().setState(stateMap.get(holder.getKey()));
}
// 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);
}
/**
* Instructs this factory to handle auto-start flag manually
* by calling lifecycle start method.
*
* @param handleAutostartup the new handle autostartup
*/
public void setHandleAutostartup(boolean handleAutostartup) {
this.handleAutostartup = handleAutostartup;
}
/**
* Instructs this factory to enable application context events.
*
* @param contextEvents the new context events enabled
*/
public void setContextEventsEnabled(Boolean contextEvents) {
this.contextEvents = contextEvents;
}
/**
* Set state machine monitor.
*
* @param stateMachineMonitor the state machine monitor
*/
public void setStateMachineMonitor(StateMachineMonitor<S, E> stateMachineMonitor) {
this.defaultStateMachineMonitor = stateMachineMonitor;
}
private StateMachine<S, E> delegateAutoStartup(StateMachine<S, E> delegate) {
if (handleAutostartup && delegate instanceof SmartLifecycle && ((SmartLifecycle) delegate).isAutoStartup()) {
AutostartListener<S, E> autostartListener = new AutostartListener<>();
delegate.addStateListener(autostartListener);
((SmartLifecycle)delegate).start();
try {
autostartListener.latch.await(30, TimeUnit.SECONDS);
} catch (Exception e) {
log.warn("Waited 30 seconds for machine to start as autostart was requested, machine may not be ready");
} finally {
delegate.removeStateListener(autostartListener);
}
}
return delegate;
}
protected BeanFactory resolveBeanFactory(StateMachineModel<S, E> stateMachineModel) {
if (stateMachineModel.getConfigurationData().getBeanFactory() != null) {
return stateMachineModel.getConfigurationData().getBeanFactory();
} else {
return getBeanFactory();
}
}
protected TaskExecutor resolveTaskExecutor(StateMachineModel<S, E> stateMachineModel) {
if (stateMachineModel.getConfigurationData().getTaskExecutor() != null) {
return stateMachineModel.getConfigurationData().getTaskExecutor();
} else {
return getTaskExecutor();
}
}
protected TaskScheduler resolveTaskScheduler(StateMachineModel<S, E> stateMachineModel) {
if (stateMachineModel.getConfigurationData().getTaskScheduler() != null) {
return stateMachineModel.getConfigurationData().getTaskScheduler();
} else {
return getTaskScheduler();
}
}
protected StateMachineModel<S, E> resolveStateMachineModel(String machineId) {
if (stateMachineModelFactory == null) {
return defaultStateMachineModel;
} else {
StateMachineModel<S, E> m = stateMachineModelFactory.build(machineId);
if (m.getConfigurationData() == null) {
// if model doesn't have explicit configuration data,
// get it from default model
return new DefaultStateMachineModel<>(defaultStateMachineModel.getConfigurationData(), m.getStatesData(),
m.getTransitionsData());
} else {
return m;
}
}
}
private int getInitialCount(Collection<StateData<S, E>> stateDatas) {
int count = 0;
for (StateData<S, E> stateData : stateDatas) {
if (stateData.isInitial()) {
count++;
}
}
return count;
}
private Collection<Collection<StateData<S, E>>> splitIntoRegions(Collection<StateData<S, E>> stateDatas) {
Map<Object, Collection<StateData<S, E>>> map = new HashMap<Object, Collection<StateData<S, E>>>();
for (StateData<S, E> stateData : stateDatas) {
Collection<StateData<S, E>> c = map.get(stateData.getRegion());
if (c == null) {
c = new ArrayList<StateData<S,E>>();
}
c.add(stateData);
map.put(stateData.getRegion(), c);
}
return map.values();
}
private Collection<TransitionData<S, E>> getTransitionData(boolean roots, Collection<StateData<S, E>> stateDatas, StateMachineModel<S, E> stateMachineModel) {
if (roots) {
return resolveTransitionData(stateMachineModel.getTransitionsData().getTransitions(), stateDatas);
} else {
return resolveTransitionData2(stateMachineModel.getTransitionsData().getTransitions());
}
}
private static <S, E> Collection<StateData<S, E>> popSameParents(Stack<StateData<S, E>> stack) {
Collection<StateData<S, E>> data = new ArrayList<StateData<S, E>>();
Object parent = null;
if (!stack.isEmpty()) {
parent = stack.peek().getParent();
}
while (!stack.isEmpty() && ObjectUtils.nullSafeEquals(parent, stack.peek().getParent())) {
data.add(stack.pop());
}
return data;
}
private static class MachineStackItem<S, E> {
StateMachine<S, E> machine;
public MachineStackItem(StateMachine<S, E> machine) {
this.machine = machine;
}
}
private Collection<TransitionData<S, E>> resolveTransitionData(Collection<TransitionData<S, E>> in, Collection<StateData<S, E>> stateDatas) {
ArrayList<TransitionData<S, E>> out = new ArrayList<TransitionData<S,E>>();
Collection<Object> states = new ArrayList<Object>();
for (StateData<S, E> stateData : stateDatas) {
states.add(stateData.getParent());
}
for (TransitionData<S, E> transitionData : in) {
S state = transitionData.getState();
if (state != null && states.contains(state)) {
out.add(transitionData);
}
}
return out;
}
private Collection<TransitionData<S, E>> resolveTransitionData2(Collection<TransitionData<S, E>> in) {
ArrayList<TransitionData<S, E>> out = new ArrayList<TransitionData<S,E>>();
for (TransitionData<S, E> transitionData : in) {
if (transitionData.getState() == null) {
out.add(transitionData);
}
}
return out;
}
@SuppressWarnings("unchecked")
private StateMachine<S, E> buildMachine(Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap,
Map<S, StateHolder<S, E>> holderMap, Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState,
TransitionsData<S, E> stateMachineTransitions, TaskExecutor taskExecutor, TaskScheduler taskScheduler, String machineId,
UUID uuid, StateMachineModel<S, E> stateMachineModel) {
State<S, E> state = null;
State<S, E> initialState = null;
PseudoState<S, E> historyState = null;
Action<S, E> initialAction = null;
Collection<State<S, E>> states = new ArrayList<State<S,E>>();
// for now loop twice and build states for
// non initial/end pseudostates last
for (StateData<S, E> stateData : stateDatas) {
StateMachine<S, E> stateMachine = machineMap.get(stateData.getState());
if (stateMachine == null) {
// get a submachine from state data if we didn't have
// it already. stays null if we don't have one.
stateMachine = stateData.getSubmachine();
if (stateMachine == null && stateData.getSubmachineFactory() != null) {
stateMachine = stateData.getSubmachineFactory().getStateMachine(machineId);
}
}
state = stateMap.get(stateData.getState());
if (state != null) {
states.add(state);
if (stateData.isInitial()) {
initialState = state;
}
continue;
}
if (stateMachine != null) {
PseudoState<S, E> pseudoState = null;
if (stateData.isInitial()) {
pseudoState = new DefaultPseudoState<S, E>(PseudoStateKind.INITIAL);
}
state = new StateMachineState<S, E>(stateData.getState(), stateMachine, stateData.getDeferred(),
stateData.getEntryActions(), stateData.getExitActions(), pseudoState);
// TODO: below if/else doesn't feel right
if (stateDatas.size() > 1 && stateData.isInitial()) {
initialState = state;
initialAction = stateData.getInitialAction();
} else if (stateDatas.size() == 1) {
initialState = state;
initialAction = stateData.getInitialAction();
}
states.add(state);
} else {
PseudoState<S, E> pseudoState = null;
if (stateData.isInitial()) {
pseudoState = new DefaultPseudoState<S, E>(PseudoStateKind.INITIAL);
} else if (stateData.isEnd()) {
pseudoState = new DefaultPseudoState<S, E>(PseudoStateKind.END);
} else if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_SHALLOW) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_DEEP) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JOIN) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.FORK) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JUNCTION) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.EXIT) {
continue;
}
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
if (stateData.isInitial()) {
initialState = state;
initialAction = stateData.getInitialAction();
}
states.add(state);
}
stateMap.put(stateData.getState(), state);
}
for (StateData<S, E> stateData : stateDatas) {
if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_SHALLOW) {
State<S, E> defaultState = null;
S s = stateData.getState();
Collection<HistoryData<S,E>> historys = stateMachineTransitions.getHistorys();
for (HistoryData<S,E> history : historys) {
if (history.getSource().equals(s)) {
defaultState = stateMap.get(history.getTarget());
}
}
StateHolder<S, E> defaultStateHolder = new StateHolder<S, E>(defaultState);
StateHolder<S, E> containingStateHolder = new StateHolder<S, E>(stateMap.get(stateData.getParent()));
if (containingStateHolder.getState() == null) {
holderMap.put((S)stateData.getParent(), containingStateHolder);
}
PseudoState<S, E> pseudoState = new HistoryPseudoState<S, E>(PseudoStateKind.HISTORY_SHALLOW, defaultStateHolder, containingStateHolder);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
historyState = pseudoState;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_DEEP) {
State<S, E> defaultState = null;
S s = stateData.getState();
Collection<HistoryData<S,E>> historys = stateMachineTransitions.getHistorys();
for (HistoryData<S,E> history : historys) {
if (history.getSource().equals(s)) {
defaultState = stateMap.get(history.getTarget());
}
}
StateHolder<S, E> defaultStateHolder = new StateHolder<S, E>(defaultState);
StateHolder<S, E> containingStateHolder = new StateHolder<S, E>(stateMap.get(stateData.getParent()));
if (containingStateHolder.getState() == null) {
holderMap.put((S)stateData.getParent(), containingStateHolder);
}
PseudoState<S, E> pseudoState = new HistoryPseudoState<S, E>(PseudoStateKind.HISTORY_DEEP, defaultStateHolder, containingStateHolder);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
historyState = pseudoState;
}
if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) {
S s = stateData.getState();
List<ChoiceData<S, E>> list = stateMachineTransitions.getChoices().get(s);
List<ChoiceStateData<S, E>> choices = new ArrayList<ChoiceStateData<S, E>>();
for (ChoiceData<S, E> c : list) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(c.getTarget()));
if (holder.getState() == null) {
holderMap.put(c.getTarget(), holder);
}
choices.add(new ChoiceStateData<S, E>(holder, c.getGuard(), c.getActions()));
}
PseudoState<S, E> pseudoState = new ChoicePseudoState<S, E>(choices);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JUNCTION) {
S s = stateData.getState();
List<JunctionData<S, E>> list = stateMachineTransitions.getJunctions().get(s);
List<JunctionStateData<S, E>> junctions = new ArrayList<JunctionStateData<S, E>>();
for (JunctionData<S, E> c : list) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(c.getTarget()));
if (holder.getState() == null) {
holderMap.put(c.getTarget(), holder);
}
junctions.add(new JunctionStateData<S, E>(holder, c.getGuard(), c.getActions()));
}
PseudoState<S, E> pseudoState = new JunctionPseudoState<S, E>(junctions);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
} else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) {
S s = stateData.getState();
Collection<EntryData<S, E>> entrys = stateMachineTransitions.getEntrys();
for (EntryData<S, E> entry : entrys) {
if (s.equals(entry.getSource())) {
PseudoState<S, E> pseudoState = new EntryPseudoState<S, E>(stateMap.get(entry.getTarget()));
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
break;
}
}
} else if (stateData.getPseudoStateKind() == PseudoStateKind.EXIT) {
S s = stateData.getState();
Collection<ExitData<S, E>> exits = stateMachineTransitions.getExits();
for (ExitData<S, E> entry : exits) {
if (s.equals(entry.getSource())) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(entry.getTarget()));
if (holder.getState() == null) {
holderMap.put(entry.getTarget(), holder);
}
PseudoState<S, E> pseudoState = new ExitPseudoState<S, E>(holder);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
break;
}
}
} else if (stateData.getPseudoStateKind() == PseudoStateKind.FORK) {
S s = stateData.getState();
List<S> list = stateMachineTransitions.getForks().get(s);
List<State<S, E>> forks = new ArrayList<State<S,E>>();
for (S fs : list) {
forks.add(stateMap.get(fs));
}
PseudoState<S, E> pseudoState = new ForkPseudoState<S, E>(forks);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JOIN) {
S s = stateData.getState();
List<S> list = stateMachineTransitions.getJoins().get(s);
List<State<S, E>> joins = new ArrayList<State<S,E>>();
// if join source is a regionstate, get
// it's end states from regions
if (list.size() == 1) {
State<S, E> ss1 = stateMap.get(list.get(0));
if (ss1 instanceof RegionState) {
Collection<Region<S, E>> regions = ((RegionState<S, E>)ss1).getRegions();
for (Region<S, E> r : regions) {
Collection<State<S, E>> ss2 = r.getStates();
for (State<S, E> ss3 : ss2) {
if (ss3.getPseudoState() != null && ss3.getPseudoState().getKind() == PseudoStateKind.END) {
joins.add(ss3);
continue;
}
}
}
}
} else {
for (S fs : list) {
joins.add(stateMap.get(fs));
}
}
List<JoinStateData<S, E>> joinTargets = new ArrayList<JoinStateData<S, E>>();
Collection<TransitionData<S, E>> transitions = stateMachineTransitions.getTransitions();
for (TransitionData<S, E> tt : transitions) {
if (tt.getSource() == s) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(tt.getTarget()));
if (holder.getState() == null) {
holderMap.put(tt.getTarget(), holder);
}
joinTargets.add(new JoinStateData<S, E>(holder, tt.getGuard()));
}
}
JoinPseudoState<S, E> pseudoState = new JoinPseudoState<S, E>(joins, joinTargets);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), stateData.getStateActions(), pseudoState, stateMachineModel);
states.add(state);
stateMap.put(stateData.getState(), state);
}
}
Collection<Transition<S, E>> transitions = new ArrayList<Transition<S, E>>();
for (TransitionData<S, E> transitionData : transitionsData) {
S source = transitionData.getSource();
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, count != null ? count : 0);
if (beanFactory != null) {
t.setBeanFactory(beanFactory);
}
if (taskExecutor != null) {
t.setTaskExecutor(taskExecutor);
}
if (taskScheduler != null) {
t.setTaskScheduler(taskScheduler);
}
trigger = t;
((AbstractState<S, E>)stateMap.get(source)).getTriggers().add(trigger);
}
if (transitionData.getKind() == TransitionKind.EXTERNAL) {
// TODO can we do this?
if (stateMap.get(source) == null || stateMap.get(target) == null) {
continue;
}
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(stateMap.get(source),
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger,
transitionData.getSecurityRule());
transitions.add(transition);
} else if (transitionData.getKind() == TransitionKind.LOCAL) {
// TODO can we do this?
if (stateMap.get(source) == null || stateMap.get(target) == null) {
continue;
}
DefaultLocalTransition<S, E> transition = new DefaultLocalTransition<S, E>(stateMap.get(source),
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger,
transitionData.getSecurityRule());
transitions.add(transition);
} else if (transitionData.getKind() == TransitionKind.INTERNAL) {
DefaultInternalTransition<S, E> transition = new DefaultInternalTransition<S, E>(stateMap.get(source),
transitionData.getActions(), event, transitionData.getGuard(), trigger,
transitionData.getSecurityRule());
transitions.add(transition);
}
}
if (stateMachineTransitions.getJoins() != null) {
for (Entry<S, List<S>> entry : stateMachineTransitions.getJoins().entrySet()) {
if (stateMap.get(entry.getKey()) != null) {
List<S> entryList = entry.getValue();
for (S entryState : entryList) {
State<S, E> source = stateMap.get(entryState);
if (source != null && !source.isOrthogonal()) {
State<S, E> target = stateMap.get(entry.getKey());
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(
source, target, null, null, null, null, null);
transitions.add(transition);
}
}
}
}
}
Transition<S, E> initialTransition = new InitialTransition<S, E>(initialState, initialAction);
StateMachine<S, E> machine = buildStateMachineInternal(states, transitions, initialState, initialTransition,
null, defaultExtendedState, historyState, contextEvents, beanFactory, taskExecutor, taskScheduler,
beanName, machineId != null ? machineId : stateMachineModel.getConfigurationData().getMachineId(), uuid, stateMachineModel);
return machine;
}
protected abstract StateMachine<S, E> buildStateMachineInternal(Collection<State<S, E>> states,
Collection<Transition<S, E>> transitions, State<S, E> initialState, Transition<S, E> initialTransition, Message<E> initialEvent,
ExtendedState extendedState, PseudoState<S, E> historyState, Boolean contextEventsEnabled, BeanFactory beanFactory,
TaskExecutor taskExecutor, TaskScheduler taskScheduler, String beanName, String machineId, UUID uuid,
StateMachineModel<S, E> stateMachineModel);
protected abstract State<S, E> buildStateInternal(S id, Collection<E> deferred,
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
Collection<? extends Action<S, E>> stateActions, PseudoState<S, E> pseudoState, StateMachineModel<S, E> stateMachineModel);
private Iterator<Node<StateData<S, E>>> buildStateDataIterator(StateMachineModel<S, E> stateMachineModel) {
Tree<StateData<S, E>> tree = new Tree<StateData<S, E>>();
treeAdd(tree, stateMachineModel.getStatesData().getStateData());
return new TreeTraverser<Node<StateData<S, E>>>() {
@Override
public Iterable<Node<StateData<S, E>>> children(Node<StateData<S, E>> root) {
return root.getChildren();
}
}.postOrderTraversal(tree.getRoot()).iterator();
}
private void treeAdd(Tree<StateData<S, E>> tree, Collection<StateData<S, E>> stateDatas) {
// recursive call due to possible submachine data ref
if (stateDatas == null) {
return;
}
for (StateData<S, E> stateData : stateDatas) {
tree.add(stateData, stateData.getState(), stateData.getParent());
treeAdd(tree, stateData.getSubmachineStateData());
}
}
protected abstract RegionState<S, E> buildRegionStateInternal(S id, Collection<Region<S, E>> regions, Collection<E> deferred,
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
PseudoState<S, E> pseudoState);
/**
* Simple utility listener waiting machine to get started if
* autostart was requestes. Needed for machine to be ready
* if async executor is used.
*/
private static class AutostartListener<S, E> extends StateMachineListenerAdapter<S, E> {
final CountDownLatch latch = new CountDownLatch(1);
@Override
public void stateMachineStarted(StateMachine<S, E> stateMachine) {
latch.countDown();
}
}
}