/*
* 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.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.ExtendedState.ExtendedStateChangeListener;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateContext.Stage;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachineException;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.access.StateMachineAccessor;
import org.springframework.statemachine.access.StateMachineFunction;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.action.ActionListener;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.monitor.StateMachineMonitor;
import org.springframework.statemachine.region.Region;
import org.springframework.statemachine.state.AbstractState;
import org.springframework.statemachine.state.ForkPseudoState;
import org.springframework.statemachine.state.HistoryPseudoState;
import org.springframework.statemachine.state.JoinPseudoState;
import org.springframework.statemachine.state.PseudoState;
import org.springframework.statemachine.state.PseudoStateContext;
import org.springframework.statemachine.state.PseudoStateKind;
import org.springframework.statemachine.state.PseudoStateListener;
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.TransitionKind;
import org.springframework.statemachine.trigger.DefaultTriggerContext;
import org.springframework.statemachine.trigger.Trigger;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Base implementation of a {@link StateMachine} loosely modelled from UML state
* machine.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSupport<S, E> implements StateMachine<S, E>, StateMachineAccess<S, E> {
private static final Log log = LogFactory.getLog(AbstractStateMachine.class);
private final Collection<State<S,E>> states;
private final Collection<Transition<S,E>> transitions;
private final State<S,E> initialState;
private final Transition<S, E> initialTransition;
private final Message<E> initialEvent;
private ExtendedState extendedState;
private volatile State<S,E> currentState;
// using this to log last state when machine stops, as
// it's a bit difficult to keep currentState non-null after stop.
private volatile State<S,E> lastState;
private volatile Exception currentError;
private volatile PseudoState<S, E> history;
private final Map<Trigger<S, E>, Transition<S,E>> triggerToTransitionMap = new HashMap<Trigger<S,E>, Transition<S,E>>();
private final List<Transition<S, E>> triggerlessTransitions = new ArrayList<Transition<S,E>>();
private StateMachine<S, E> relay;
private StateMachineExecutor<S, E> stateMachineExecutor;
private Boolean initialEnabled = null;
private final UUID uuid;
private String id;
private volatile Message<E> forwardedInitialEvent;
private final Object lock = new Object();
private StateMachine<S, E> parentMachine;
/**
* Instantiates a new abstract state machine.
*
* @param states the states of this machine
* @param transitions the transitions of this machine
* @param initialState the initial state of this machine
*/
public AbstractStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState) {
this(states, transitions, initialState, new DefaultExtendedState());
}
/**
* Instantiates a new abstract state machine.
*
* @param states the states of this machine
* @param transitions the transitions of this machine
* @param initialState the initial state of this machine
* @param extendedState the extended state of this machine
*/
public AbstractStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState, ExtendedState extendedState) {
this(states, transitions, initialState, null, null, extendedState, null);
}
/**
* Instantiates a new abstract state machine.
*
* @param states the states of this machine
* @param transitions the transitions of this machine
* @param initialState the initial state of this machine
* @param initialTransition the initial transition
* @param initialEvent the initial event of this machine
* @param extendedState the extended state of this machine
* @param uuid the given uuid for this machine
*/
public AbstractStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState, Transition<S, E> initialTransition, Message<E> initialEvent,
ExtendedState extendedState, UUID uuid) {
super();
this.uuid = uuid == null ? UUID.randomUUID() : uuid;
this.states = states;
this.transitions = transitions;
this.initialState = initialState;
this.initialEvent = initialEvent;
this.extendedState = extendedState != null ? extendedState : new DefaultExtendedState();
if (initialTransition == null) {
this.initialTransition = new InitialTransition<S, E>(initialState);
} else {
this.initialTransition = initialTransition;
}
}
@Override
public State<S,E> getState() {
// if we're complete assume we're stopped
// and state was stashed into lastState
synchronized (lock) {
if (lastState != null && isComplete()) {
return lastState;
} else {
return currentState;
}
}
}
@Override
public State<S,E> getInitialState() {
return initialState;
}
@Override
public ExtendedState getExtendedState() {
return extendedState;
}
/**
* @param history to set internal history state.
*/
public void setHistoryState(PseudoState<S, E> history) {
this.history = history;
}
/**
* @return history state attribute.
*/
public PseudoState<S, E> getHistoryState() {
return history;
}
@Override
public boolean sendEvent(Message<E> event) {
if (hasStateMachineError()) {
// TODO: should we throw exception?
notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
return false;
}
try {
event = getStateMachineInterceptors().preEvent(event, this);
} catch (Exception e) {
log.info("Event " + event + " threw exception in interceptors, not accepting event");
notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
return false;
}
if (isComplete() || !isRunning()) {
notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
return false;
}
boolean accepted = acceptEvent(event);
stateMachineExecutor.execute();
if (!accepted) {
notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
}
return accepted;
}
@Override
protected void notifyEventNotAccepted(StateContext<S, E> stateContext) {
if (parentMachine == null) {
super.notifyEventNotAccepted(stateContext);
}
}
@Override
public boolean sendEvent(E event) {
return sendEvent(MessageBuilder.withPayload(event).build());
}
@Override
protected void onInit() throws Exception {
super.onInit();
Assert.notNull(initialState, "Initial state must be set");
Assert.state(initialState.getPseudoState() != null
&& initialState.getPseudoState().getKind() == PseudoStateKind.INITIAL,
"Initial state's pseudostate kind must be INITIAL");
lastState = null;
extendedState.setExtendedStateChangeListener(new ExtendedStateChangeListener() {
@Override
public void changed(Object key, Object value) {
notifyExtendedStateChanged(key, value, buildStateContext(Stage.EXTENDED_STATE_CHANGED, null, null, getRelayStateMachine()));
}
});
// process given transitions
for (Transition<S, E> transition : transitions) {
Trigger<S, E> trigger = transition.getTrigger();
if (trigger != null) {
// we have same triggers with different transitions
triggerToTransitionMap.put(trigger, transition);
} else {
triggerlessTransitions.add(transition);
}
}
for (State<S, E> state : states) {
if (state.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)state).getSubmachine();
submachine.addStateListener(new StateMachineListenerRelay());
} else if (state.isOrthogonal()) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>)state).getRegions();
for (Region<S, E> region : regions) {
region.addStateListener(new StateMachineListenerRelay());
}
}
if (state.getPseudoState() != null
&& (state.getPseudoState().getKind() == PseudoStateKind.HISTORY_DEEP || state.getPseudoState()
.getKind() == PseudoStateKind.HISTORY_DEEP)) {
history = state.getPseudoState();
}
}
DefaultStateMachineExecutor<S, E> executor = new DefaultStateMachineExecutor<S, E>(this, getRelayStateMachine(), transitions,
triggerToTransitionMap, triggerlessTransitions, initialTransition, initialEvent);
if (getBeanFactory() != null) {
executor.setBeanFactory(getBeanFactory());
}
if (getTaskExecutor() != null){
executor.setTaskExecutor(getTaskExecutor());
}
executor.afterPropertiesSet();
executor.setStateMachineExecutorTransit(new StateMachineExecutorTransit<S, E>() {
@Override
public void transit(Transition<S, E> t, StateContext<S, E> ctx, Message<E> message) {
long now = System.currentTimeMillis();
// TODO: fix above stateContext as it's not used
notifyTransitionStart(buildStateContext(Stage.TRANSITION_START, message, t, getRelayStateMachine()));
try {
t.executeTransitionActions(ctx);
} catch (Exception e) {
// aborting, executor should stop possible loop checking possible transitions
// causing infinite execution
log.warn("Aborting as transition " + t + " caused error " + e);
throw new StateMachineException("Aborting as transition " + t + " caused error ", e);
}
notifyTransition(buildStateContext(Stage.TRANSITION, message, t, getRelayStateMachine()));
if (t.getTarget().getPseudoState() != null && t.getTarget().getPseudoState().getKind() == PseudoStateKind.JOIN) {
exitFromState(t.getSource(), message, t, getRelayStateMachine());
} else {
if (t.getKind() == TransitionKind.INITIAL) {
switchToState(t.getTarget(), message, t, getRelayStateMachine());
notifyStateMachineStarted(buildStateContext(Stage.STATEMACHINE_START, message, t, getRelayStateMachine()));
} else if (t.getKind() != TransitionKind.INTERNAL) {
switchToState(t.getTarget(), message, t, getRelayStateMachine());
}
}
// TODO: looks like events should be called here and anno processing earlier
notifyTransitionEnd(buildStateContext(Stage.TRANSITION_END, message, t, getRelayStateMachine()));
notifyTransitionMonitor(getRelayStateMachine(), t, System.currentTimeMillis() - now);
}
});
stateMachineExecutor = executor;
for (Transition<S, E> t : getTransitions()) {
t.addActionListener(new ActionListener<S, E>() {
@Override
public void onExecute(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) {
notifyActionMonitor(stateMachine, action, duration);
}
});
}
for (State<S, E> s : getStates()) {
s.addActionListener(new ActionListener<S, E>() {
@Override
public void onExecute(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) {
notifyActionMonitor(stateMachine, action, duration);
}
});
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// last change to set factory because this maybe be called per
// BeanFactoryAware if machine is created as Bean and configurers
// didn't set it.
if (getBeanFactory() == null) {
super.setBeanFactory(beanFactory);
if (stateMachineExecutor instanceof BeanFactoryAware) {
((BeanFactoryAware)stateMachineExecutor).setBeanFactory(beanFactory);
}
}
}
@Override
protected void doStart() {
super.doStart();
// if state is set assume nothing to do
if (currentState != null) {
if (log.isDebugEnabled()) {
log.debug("State already set, disabling initial");
}
registerPseudoStateListener();
stateMachineExecutor.setInitialEnabled(false);
stateMachineExecutor.start();
// assume that state was set/reseted so we need to
// dispatch started event which would net getting
// dispatched via executor
StateContext<S, E> stateContext = buildStateContext(Stage.STATEMACHINE_START, null, null, getRelayStateMachine());
notifyStateMachineStarted(stateContext);
return;
}
registerPseudoStateListener();
if (initialEnabled != null && !initialEnabled) {
if (log.isDebugEnabled()) {
log.debug("Initial disable asked, disabling initial");
}
stateMachineExecutor.setInitialEnabled(false);
} else {
stateMachineExecutor.setForwardedInitialEvent(forwardedInitialEvent);
}
// start fires first execution which should execute initial transition
stateMachineExecutor.start();
}
@Override
protected void doStop() {
synchronized (lock) {
stateMachineExecutor.stop();
notifyStateMachineStopped(buildStateContext(Stage.STATEMACHINE_STOP, null, null, this));
// stash current state before we null it so that
// we can still return where we 'were' when machine is stopped
lastState = currentState;
currentState = null;
initialEnabled = null;
}
}
@Override
protected void doDestroy() {
// if lifecycle methods has not been called, make
// sure we get into those if only destroy() is called.
stop();
}
@Override
public void setStateMachineError(Exception exception) {
if (exception == null) {
currentError = null;
} else {
exception = getStateMachineInterceptors().stateMachineError(this, exception);
currentError = exception;
}
if (currentError != null) {
notifyStateMachineError(buildStateContext(Stage.STATEMACHINE_ERROR, null, null, this, currentError));
}
}
@Override
public boolean hasStateMachineError() {
return currentError != null;
}
@Override
public void addStateListener(StateMachineListener<S, E> listener) {
getStateListener().register(listener);
}
@Override
public void removeStateListener(StateMachineListener<S, E> listener) {
getStateListener().unregister(listener);
}
@Override
public boolean isComplete() {
State<S, E> s = currentState;
if (s == null) {
return !isRunning();
} else {
return s != null && s.getPseudoState() != null
&& s.getPseudoState().getKind() == PseudoStateKind.END;
}
}
/**
* Gets the {@link State}s defined in this machine. Returned collection is
* an unmodifiable copy because states in a state machine are immutable.
*
* @return immutable copy of existing states
*/
@Override
public Collection<State<S, E>> getStates() {
return Collections.unmodifiableCollection(states);
}
@Override
public Collection<Transition<S, E>> getTransitions() {
return transitions;
}
@Override
public void setInitialEnabled(boolean enabled) {
initialEnabled = enabled;
}
@SuppressWarnings("unchecked")
@Override
public StateMachineAccessor<S, E> getStateMachineAccessor() {
// TODO: needs cleaning and perhaps not an anonymous function
return new StateMachineAccessor<S, E>() {
@Override
public void doWithAllRegions(StateMachineFunction<StateMachineAccess<S, E>> stateMachineAccess) {
stateMachineAccess.apply(AbstractStateMachine.this);
for (State<S, E> state : states) {
if (state.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>) state).getSubmachine();
submachine.getStateMachineAccessor().doWithAllRegions(stateMachineAccess);
} else if (state.isOrthogonal()) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>) state).getRegions();
for (Region<S, E> region : regions) {
((StateMachine<S, E>)region).getStateMachineAccessor().doWithAllRegions(stateMachineAccess);
}
}
}
}
@Override
public List<StateMachineAccess<S, E>> withAllRegions() {
List<StateMachineAccess<S, E>> list = new ArrayList<StateMachineAccess<S, E>>();
list.add(AbstractStateMachine.this);
for (State<S, E> state : states) {
if (state.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>) state).getSubmachine();
if (submachine instanceof StateMachineAccess) {
list.add((StateMachineAccess<S, E>)submachine);
}
} else if (state.isOrthogonal()) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>) state).getRegions();
for (Region<S, E> region : regions) {
list.add((StateMachineAccess<S, E>) region);
}
}
}
return list;
}
@Override
public void doWithRegion(StateMachineFunction<StateMachineAccess<S, E>> stateMachineAccess) {
stateMachineAccess.apply(AbstractStateMachine.this);
}
@Override
public StateMachineAccess<S, E> withRegion() {
return AbstractStateMachine.this;
}
};
}
@Override
public void setRelay(StateMachine<S, E> stateMachine) {
this.relay = stateMachine;
}
@Override
public void setParentMachine(StateMachine<S, E> parentMachine) {
this.parentMachine = parentMachine;
}
@Override
protected void stateChangedInRelay() {
// TODO: temp tweak, see super
stateMachineExecutor.execute();
}
@Override
public void setForwardedInitialEvent(Message<E> message) {
forwardedInitialEvent = message;
}
private StateMachine<S, E> getRelayStateMachine() {
return relay != null ? relay : this;
}
@Override
public String toString() {
ArrayList<State<S, E>> all = new ArrayList<State<S,E>>();
for (State<S, E> s : states) {
all.addAll(s.getStates());
}
StringBuilder buf = new StringBuilder();
for (State<S, E> s : all) {
buf.append(s.getId() + " ");
}
buf.append(" / ");
if (currentState != null) {
buf.append(StringUtils.collectionToCommaDelimitedString(currentState.getIds()));
}
buf.append(" / uuid=");
buf.append(uuid);
buf.append(" / id=");
buf.append(id);
return buf.toString();
}
@Override
public void resetStateMachine(StateMachineContext<S, E> stateMachineContext) {
// TODO: this function needs a serious rewrite
if (stateMachineContext == null) {
log.info("Got null context, resetting to initial state and clearing extended state");
currentState = initialState;
extendedState.getVariables().clear();
return;
}
if (log.isDebugEnabled()) {
log.debug("Request to reset state machine: stateMachine=[" + this + "] stateMachineContext=[" + stateMachineContext + "]");
}
setId(stateMachineContext.getId());
S state = stateMachineContext.getState();
boolean stateSet = false;
// handle state reset
for (State<S, E> s : getStates()) {
for (State<S, E> ss : s.getStates()) {
if (state != null && ss.getIds().contains(state)) {
currentState = s;
// setting lastState here is needed for restore
lastState = currentState;
// TODO: not sure about starting submachine/regions here, though
// needed if we only transit to super state or reset regions
if (s.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)s).getSubmachine();
for (final StateMachineContext<S, E> child : stateMachineContext.getChilds()) {
submachine.getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S,E>>() {
@Override
public void apply(StateMachineAccess<S, E> function) {
function.resetStateMachine(child);
}
});
}
submachine.start();
} else if (s.isOrthogonal() && stateMachineContext.getChilds() != null) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>)s).getRegions();
for (Region<S, E> region : regions) {
for (final StateMachineContext<S, E> child : stateMachineContext.getChilds()) {
((StateMachine<S, E>)region).getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S,E>>() {
@Override
public void apply(StateMachineAccess<S, E> function) {
function.resetStateMachine(child);
}
});
}
}
for (Region<S, E> region : regions) {
region.start();
}
}
if (log.isDebugEnabled()) {
log.debug("State reseted: stateMachine=[" + this + "] stateMachineContext=[" + stateMachineContext + "]");
}
stateSet = true;
break;
} else if (!stateMachineContext.getChilds().isEmpty()) {
// we're here because root machine only have regions
if (s.isOrthogonal()) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>)s).getRegions();
for (Region<S, E> region : regions) {
for (final StateMachineContext<S, E> child : stateMachineContext.getChilds()) {
((StateMachine<S, E>)region).getStateMachineAccessor().doWithRegion(new StateMachineFunction<StateMachineAccess<S,E>>() {
@Override
public void apply(StateMachineAccess<S, E> function) {
function.resetStateMachine(child);
}
});
}
}
for (Region<S, E> region : regions) {
region.start();
}
}
}
}
if (stateSet) {
break;
}
}
// handle history reset here as above state reset loop breaks out
if (history != null && stateMachineContext.getHistoryStates() != null) {
// setting history for 'this' machine
State<S, E> h = null;
for (State<S, E> hh : getStates()) {
if (hh.getId().equals(stateMachineContext.getHistoryStates().get(null))) {
h = hh;
break;
}
}
if (h != null) {
((HistoryPseudoState<S, E>) history).setState(h);
}
}
for (State<S, E> s : getStates()) {
if (StateMachineUtils.isPseudoState(s, PseudoStateKind.JOIN)) {
JoinPseudoState<S, E> jps = (JoinPseudoState<S, E>) s.getPseudoState();
Collection<S> ids = currentState.getIds();
jps.reset(ids);
}
// setting history for 'submachines'
if (s.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>) s).getSubmachine();
PseudoState<S, E> submachineHistory = ((AbstractStateMachine<S, E>) submachine).getHistoryState();
if (submachineHistory != null) {
State<S, E> h = null;
for (State<S, E> hh : submachine.getStates()) {
if (hh.getId().equals(stateMachineContext.getHistoryStates().get(s.getId()))) {
h = hh;
break;
}
}
if (h != null) {
((HistoryPseudoState<S, E>) submachineHistory).setState(h);
}
}
}
}
if (stateSet && stateMachineContext.getExtendedState() != null) {
this.extendedState = stateMachineContext.getExtendedState();
}
}
@Override
public void addStateMachineInterceptor(StateMachineInterceptor<S, E> interceptor) {
getStateMachineInterceptors().add(interceptor);
stateMachineExecutor.addStateMachineInterceptor(interceptor);
}
@Override
public void addStateMachineMonitor(StateMachineMonitor<S, E> monitor) {
getStateMachineMonitor().register(monitor);
}
@Override
public UUID getUuid() {
return uuid;
}
@Override
public String getId() {
return id;
}
/**
* Sets the machine id.
*
* @param id the new machine id
*/
public void setId(String id) {
this.id = id;
}
protected synchronized boolean acceptEvent(Message<E> message) {
if ((currentState != null && currentState.shouldDefer(message))) {
log.info("Current state " + currentState + " deferred event " + message);
stateMachineExecutor.queueDeferredEvent(message);
return true;
}
if ((currentState != null && currentState.sendEvent(message))) {
return true;
}
if (log.isDebugEnabled()) {
log.debug("Queue event " + message + " " + this);
}
for (Transition<S,E> transition : transitions) {
State<S,E> source = transition.getSource();
Trigger<S, E> trigger = transition.getTrigger();
if (StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
if (trigger != null && trigger.evaluate(new DefaultTriggerContext<S, E>(message.getPayload()))) {
stateMachineExecutor.queueEvent(message);
return true;
}
}
}
// if we're about to not accept event, check defer again in case
// state was changed between original check and now
if ((currentState != null && currentState.shouldDefer(message))) {
log.info("Current state " + currentState + " deferred event " + message);
stateMachineExecutor.queueDeferredEvent(message);
return true;
}
return false;
}
private boolean callPreStateChangeInterceptors(State<S,E> state, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
try {
getStateMachineInterceptors().preStateChange(state, message, transition, stateMachine);
} catch (Exception e) {
log.info("Interceptors threw exception, skipping state change", e);
return false;
}
return true;
}
private void callPostStateChangeInterceptors(State<S,E> state, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
try {
getStateMachineInterceptors().postStateChange(state, message, transition, stateMachine);
} catch (Exception e) {
}
}
private boolean isInitialTransition(Transition<S,E> transition) {
return transition != null && transition.getKind() == TransitionKind.INITIAL;
}
private void switchToState(State<S,E> state, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
if (!isInitialTransition(transition) && !StateMachineUtils.isTransientPseudoState(state)
&& !callPreStateChangeInterceptors(state, message, transition, stateMachine)) {
return;
}
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine);
State<S,E> toState = followLinkedPseudoStates(state, stateContext);
PseudoStateKind kind = state.getPseudoState() != null ? state.getPseudoState().getKind() : null;
if (kind != null && (kind != PseudoStateKind.INITIAL && kind != PseudoStateKind.JOIN && kind != PseudoStateKind.FORK)) {
callPreStateChangeInterceptors(toState, message, transition, stateMachine);
}
// need to check for from original state passed in
kind = toState.getPseudoState() != null ? toState.getPseudoState().getKind() : null;
if (kind == PseudoStateKind.FORK) {
exitCurrentState(toState, message, transition, stateMachine);
ForkPseudoState<S, E> fps = (ForkPseudoState<S, E>) toState.getPseudoState();
for (State<S, E> ss : fps.getForks()) {
callPreStateChangeInterceptors(ss, message, transition, stateMachine);
setCurrentState(ss, message, transition, false, stateMachine, null, fps.getForks());
}
} else {
Collection<State<S, E>> targets = new ArrayList<>();
targets.add(toState);
setCurrentState(toState, message, transition, true, stateMachine, null, targets);
}
callPostStateChangeInterceptors(toState, message, transition, stateMachine);
stateMachineExecutor.execute();
if (isComplete()) {
stop();
}
}
private State<S,E> followLinkedPseudoStates(State<S,E> state, StateContext<S, E> stateContext) {
PseudoStateKind kind = state.getPseudoState() != null ? state.getPseudoState().getKind() : null;
if (kind == PseudoStateKind.INITIAL || kind == PseudoStateKind.FORK) {
return state;
} else if (kind != null) {
State<S,E> toState = state.getPseudoState().entry(stateContext);
if (toState == null) {
return state;
} else {
return followLinkedPseudoStates(toState, stateContext);
}
} else {
return state;
}
}
private void registerPseudoStateListener() {
for (State<S, E> state : states) {
PseudoState<S, E> p = state.getPseudoState();
if (p != null) {
List<PseudoStateListener<S, E>> listeners = new ArrayList<PseudoStateListener<S, E>>();
listeners.add(new PseudoStateListener<S, E>() {
@Override
public void onContext(PseudoStateContext<S, E> context) {
PseudoState<S, E> pseudoState = context.getPseudoState();
State<S, E> toStateOrig = findStateWithPseudoState(pseudoState);
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_EXIT, null, null, getRelayStateMachine());
State<S, E> toState = followLinkedPseudoStates(toStateOrig, stateContext);
// TODO: try to find matching transition based on direct link.
// should make this built-in in pseudostates
Transition<S, E> transition = findTransition(toStateOrig, toState);
switchToState(toState, null, transition, getRelayStateMachine());
pseudoState.exit(stateContext);
}
});
// setting instead adding makes sure existing listeners are removed
p.setPseudoStateListeners(listeners);
}
}
}
private Transition<S, E> findTransition(State<S, E> from, State<S, E> to) {
for (Transition<S, E> transition : transitions) {
if (transition.getSource() == from && transition.getTarget() == to) {
return transition;
}
}
return null;
}
private State<S, E> findStateWithPseudoState(PseudoState<S, E> pseudoState) {
for (State<S, E> s : states) {
if (s.getPseudoState() == pseudoState) {
return s;
}
}
return null;
}
private StateContext<S, E> buildStateContext(Stage stage, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(stage, message, messageHeaders, extendedState, transition, stateMachine, null, null, null);
}
private StateContext<S, E> buildStateContext(Stage stage, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, Exception exception) {
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(stage, message, messageHeaders, extendedState, transition, stateMachine, null, null, exception);
}
private StateContext<S, E> buildStateContext(Stage stage, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target) {
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(stage, message, messageHeaders, extendedState, transition, stateMachine, source, target, null);
}
private StateContext<S, E> buildStateContext(Stage stage, Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, Collection<State<S, E>> sources, Collection<State<S, E>> targets) {
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(stage, message, messageHeaders, extendedState, transition, stateMachine, null, null, sources, targets, null);
}
private State<S, E> findDeepParent(State<S, E> state) {
for (State<S, E> s : states) {
if (s.getStates().contains(state)) {
return s;
}
}
return null;
}
synchronized void setCurrentState(State<S, E> state, Message<E> message, Transition<S, E> transition, boolean exit, StateMachine<S, E> stateMachine) {
setCurrentState(state, message, transition, exit, stateMachine, null, null);
}
synchronized void setCurrentState(State<S, E> state, Message<E> message, Transition<S, E> transition, boolean exit,
StateMachine<S, E> stateMachine, Collection<State<S, E>> sources, Collection<State<S, E>> targets) {
State<S, E> findDeep = findDeepParent(state);
boolean isTargetSubOf = false;
if (transition != null) {
isTargetSubOf = StateMachineUtils.isSubstate(state, transition.getSource());
if (isTargetSubOf && currentState == transition.getTarget()) {
state = transition.getSource();
}
}
boolean nonDeepStatePresent = false;
if (states.contains(state)) {
if (exit) {
exitCurrentState(state, message, transition, stateMachine, sources, targets);
}
State<S, E> notifyFrom = currentState;
currentState = state;
if (!isRunning()) {
start();
}
entryToState(state, message, transition, stateMachine);
notifyStateChanged(buildStateContext(Stage.STATE_CHANGED, message, null, getRelayStateMachine(), notifyFrom, state));
nonDeepStatePresent = true;
} else if (currentState == null && StateMachineUtils.isSubstate(findDeep, state)) {
if (exit) {
exitCurrentState(findDeep, message, transition, stateMachine, sources, targets);
}
State<S, E> notifyFrom = currentState;
currentState = findDeep;
if (!isRunning()) {
start();
}
entryToState(findDeep, message, transition, stateMachine);
notifyStateChanged(buildStateContext(Stage.STATE_CHANGED, message, null, getRelayStateMachine(), notifyFrom, findDeep));
}
if (currentState != null && !nonDeepStatePresent) {
if (findDeep != null) {
if (exit) {
exitCurrentState(state, message, transition, stateMachine, sources, targets);
}
if (currentState == findDeep) {
if (currentState.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
// need to check complete as submachine may now return non null
if (!submachine.isComplete() && submachine.getState() == state) {
if (currentState == findDeep) {
if (isTargetSubOf) {
entryToState(currentState, message, transition, stateMachine);
}
currentState = findDeep;
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, message, transition, false, stateMachine);
return;
}
}
} else if (currentState.isOrthogonal()) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>)currentState).getRegions();
for (Region<S, E> region : regions) {
if (region.getState() == state) {
if (currentState == findDeep) {
if (isTargetSubOf) {
entryToState(currentState, message, transition, stateMachine);
}
currentState = findDeep;
((AbstractStateMachine<S, E>)region).setCurrentState(state, message, transition, false, stateMachine);
return;
}
}
}
}
}
boolean shouldTryEntry = findDeep != currentState;
if (!shouldTryEntry && (transition.getSource() == currentState && StateMachineUtils.isSubstate(currentState, transition.getTarget()))) {
shouldTryEntry = true;
}
currentState = findDeep;
if (shouldTryEntry) {
entryToState(currentState, message, transition, stateMachine, sources, targets);
}
if (currentState.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, message, transition, false, stateMachine);
} else if (currentState.isOrthogonal()) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>)currentState).getRegions();
for (Region<S, E> region : regions) {
((AbstractStateMachine<S, E>)region).setCurrentState(state, message, transition, false, stateMachine);
}
}
}
}
if (history != null && transition.getKind() != TransitionKind.INITIAL) {
// do not set history if this is initial transition as
// it would break history state set via reset as
// we get here i.e. when machine is started in reset.
// and it really doesn't make sense to set initial state for history
// if we get here via initial transition
if (history.getKind() == PseudoStateKind.HISTORY_SHALLOW) {
((HistoryPseudoState<S, E>)history).setState(findDeep);
} else if (history.getKind() == PseudoStateKind.HISTORY_DEEP){
((HistoryPseudoState<S, E>)history).setState(state);
}
}
// if state was set from parent and we're now complete
// also initiate stop
if (stateMachine != this && isComplete()) {
stop();
}
}
void exitCurrentState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
exitCurrentState(state, message, transition, stateMachine, null, null);
}
void exitCurrentState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine,
Collection<State<S, E>> sources, Collection<State<S, E>> targets) {
if (currentState == null) {
return;
}
if (currentState.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
((AbstractStateMachine<S, E>)submachine).exitCurrentState(state, message, transition, stateMachine);
exitFromState(currentState, message, transition, stateMachine, sources, targets);
} else if (currentState.isOrthogonal()) {
Collection<Region<S,E>> regions = ((AbstractState<S, E>)currentState).getRegions();
for (Region<S,E> r : regions) {
if (r.getStates().contains(state)) {
exitFromState(r.getState(), message, transition, stateMachine, sources, targets);
}
}
exitFromState(currentState, message, transition, stateMachine, sources, targets);
} else {
exitFromState(currentState, message, transition, stateMachine, sources, targets);
}
}
private void exitFromState(State<S, E> state, Message<E> message, Transition<S, E> transition,
StateMachine<S, E> stateMachine) {
exitFromState(state, message, transition, stateMachine, null, null);
}
private void exitFromState(State<S, E> state, Message<E> message, Transition<S, E> transition,
StateMachine<S, E> stateMachine, Collection<State<S, E>> sources, Collection<State<S, E>> targets) {
if (state == null) {
return;
}
if (log.isTraceEnabled()) {
log.trace("Trying Exit state=[" + state + "]");
}
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_EXIT, message, transition, stateMachine);
if (transition != null) {
State<S, E> findDeep = findDeepParent(transition.getTarget());
boolean isTargetSubOfOtherState = findDeep != null && findDeep != currentState;
boolean isSubOfSource = StateMachineUtils.isSubstate(transition.getSource(), currentState);
boolean isSubOfTarget = StateMachineUtils.isSubstate(transition.getTarget(), currentState);
if (transition.getKind() == TransitionKind.LOCAL && StateMachineUtils.isSubstate(transition.getSource(), transition.getTarget()) && transition.getSource() == currentState) {
return;
} else if (transition.getKind() == TransitionKind.LOCAL && StateMachineUtils.isSubstate(transition.getTarget(), transition.getSource()) && transition.getTarget() == currentState) {
return;
}
// TODO: this and entry below should be done via a separate
// voter of some sort which would reveal transition path
// we could make a choice on.
if (currentState == transition.getSource() && currentState == transition.getTarget()) {
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getSource()) {
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
} else if (isTargetSubOfOtherState) {
} else if (!isSubOfSource && !isSubOfTarget && findDeep == null) {
} else if (!isSubOfSource && !isSubOfTarget && (transition.getSource() == currentState && StateMachineUtils.isSubstate(currentState, transition.getTarget()))) {
} else if (StateMachineUtils.isNormalPseudoState(transition.getTarget())) {
if (isPseudoStateSubstate(findDeep, targets)) {
return;
}
} else if (findDeep != null && findDeep != state && findDeep.getStates().contains(state)) {
} else if (!isSubOfSource && !isSubOfTarget) {
return;
}
}
if (log.isDebugEnabled()) {
log.debug("Exit state=[" + state + "]");
}
state.exit(stateContext);
notifyStateExited(buildStateContext(Stage.STATE_EXIT, message, null, getRelayStateMachine(), state, null));
}
private boolean isPseudoStateSubstate(State<S, E> left, Collection<State<S, E>> rights) {
if (rights == null || left == null) {
return false;
}
for (State<S, E> s : rights) {
if (StateMachineUtils.isSubstate(left, s)) {
return true;
}
}
return false;
}
private void entryToState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
entryToState(state, message, transition, stateMachine, null, null);
}
private void entryToState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine,
Collection<State<S, E>> sources, Collection<State<S, E>> targets) {
if (state == null) {
return;
}
if (log.isTraceEnabled()) {
log.trace("Trying Enter state=[" + state + "]");
}
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_ENTRY, message, transition, stateMachine, sources, targets);
if (transition != null) {
State<S, E> findDeep1 = findDeepParent(transition.getTarget());
State<S, E> findDeep2 = findDeepParent(transition.getSource());
boolean isComingFromOtherSubmachine = findDeep1 != null && findDeep2 != null && findDeep2 != currentState;
boolean isSubOfSource = StateMachineUtils.isSubstate(transition.getSource(), currentState);
boolean isSubOfTarget = StateMachineUtils.isSubstate(transition.getTarget(), currentState);
if (transition.getKind() == TransitionKind.LOCAL && StateMachineUtils.isSubstate(transition.getSource(), transition.getTarget())
&& transition.getSource() == currentState) {
return;
} else if (transition.getKind() == TransitionKind.LOCAL && StateMachineUtils.isSubstate(transition.getTarget(), transition.getSource())
&& transition.getTarget() == currentState) {
return;
}
if (currentState == transition.getSource() && currentState == transition.getTarget()) {
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
} else if (isComingFromOtherSubmachine) {
} else if (!isSubOfSource && !isSubOfTarget && findDeep2 == null) {
} else if (isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
if (isDirectSubstate(transition.getSource(), transition.getTarget()) && transition.getKind() != TransitionKind.LOCAL
&& isInitial(transition.getTarget())) {
return;
}
} else if (!isSubOfSource && !isSubOfTarget
&& (transition.getSource() == currentState && StateMachineUtils.isSubstate(currentState, transition.getTarget()))) {
} else if (!isSubOfSource && !isSubOfTarget) {
if (!StateMachineUtils.isTransientPseudoState(transition.getTarget())) {
return;
}
}
}
// with linked joins, we need to enter state but should not notify.
// state entries are needed to track join logic.
if (!StateMachineUtils.isPseudoState(state, PseudoStateKind.JOIN)) {
notifyStateEntered(buildStateContext(Stage.STATE_ENTRY, message, transition, getRelayStateMachine(), null, state));
}
if (log.isDebugEnabled()) {
log.debug("Enter state=[" + state + "]");
}
state.entry(stateContext);
}
private static <S, E> boolean isInitial(State<S, E> state) {
return state.getPseudoState() != null && state.getPseudoState().getKind() == PseudoStateKind.INITIAL;
}
private static <S, E> boolean isDirectSubstate(State<S, E> left, State<S, E> right) {
// Checks if right hand side is a direct substate of a left hand side.
if (left != null && left.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)left).getSubmachine();
return submachine.getStates().contains(right);
} else {
return false;
}
}
}