package org.squirrelframework.foundation.fsm.impl; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.squirrelframework.foundation.component.Observable; import org.squirrelframework.foundation.component.SquirrelProvider; import org.squirrelframework.foundation.component.impl.AbstractSubject; import org.squirrelframework.foundation.event.AsyncEventListener; import org.squirrelframework.foundation.event.ListenerMethod; import org.squirrelframework.foundation.exception.ErrorCodes; import org.squirrelframework.foundation.exception.TransitionException; import org.squirrelframework.foundation.fsm.*; import org.squirrelframework.foundation.fsm.ActionExecutionService.*; import org.squirrelframework.foundation.fsm.annotation.*; import org.squirrelframework.foundation.util.Pair; import org.squirrelframework.foundation.util.ReflectUtils; import org.squirrelframework.foundation.util.TypeReference; import java.lang.annotation.Annotation; import java.lang.reflect.*; import java.util.*; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static com.google.common.base.Preconditions.checkState; /** * The Abstract state machine provide several extension ability to cover different extension granularity. * <ol> * <li>Method <b>beforeStateExit</b>/<b>afterStateEntry</b> is used to add custom logic on all kinds of state exit/entry.</li> * <li>Method <b>exit[stateName]</b>/<b>entry[stateName]</b> is extension method which is used to add custom logic on specific state.</li> * <li>Method <b>beforeTransitionBegin</b>/<b>afterTransitionComplete</b> is used to add custom logic on all kinds of transition * accepted all conditions.</li> * <li>Method <b>transitFrom[fromStateName]To[toStateName]On[eventName]</b> is used to add custom logic on specific transition * accepted all conditions.</li> * <li>Method <b>transitFromAnyTo[toStateName]On[eventName]</b> is used to add custom logic on any state transfer to specific target * state on specific event happens, so as the <b>transitFrom[fromStateName]ToAnyOn[eventName]</b>, <b>transitFrom[fromState]To[ToStateName]</b>, * and <b>on[EventName]</b>.</li> * </ol> * @author Henry.He * * @param <T> state machine type * @param <S> state type * @param <E> event type * @param <C> context type */ public abstract class AbstractStateMachine<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractSubject implements StateMachine<T, S, E, C> { private static final Logger logger = LoggerFactory.getLogger(AbstractStateMachine.class); private final ActionExecutionService<T, S, E, C> executor = SquirrelProvider.getInstance().newInstance( new TypeReference<ActionExecutionService<T, S, E, C>>(){}); private StateMachineData<T, S, E, C> data; private volatile StateMachineStatus status = StateMachineStatus.INITIALIZED; private LinkedBlockingDeque<Pair<E, C>> queuedEvents = new LinkedBlockingDeque<Pair<E, C>>(); private LinkedBlockingQueue<Pair<E, C>> queuedTestEvents = new LinkedBlockingQueue<Pair<E, C>>(); private volatile boolean isProcessingTestEvent = false; private E startEvent, finishEvent, terminateEvent; protected final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); protected final Lock writeLock = rwLock.writeLock(); protected final Lock readLock = rwLock.readLock(); private MvelScriptManager scriptManager; // state machine options private boolean isAutoStartEnabled = true; private boolean isAutoTerminateEnabled = true; private boolean isDelegatorModeEnabled = false; @SuppressWarnings("unused") private long transitionTimeout = -1; private boolean isDataIsolateEnabled = false; private boolean isDebugModeEnabled = false; private boolean isRemoteMonitorEnabled = false; private Class<?>[] extraParamTypes; private TransitionException lastException = null; void prePostConstruct(S initialStateId, Map<S, ? extends ImmutableState<T, S, E, C>> states, StateMachineConfiguration configuration, Runnable cb) { data = FSM.newStateMachineData(states); data.write().initialState(initialStateId); data.write().currentState(null); data.write().identifier(configuration.getIdProvider().get()); // retrieve options value from state machine configuration this.isAutoStartEnabled = configuration.isAutoStartEnabled(); this.isAutoTerminateEnabled = configuration.isAutoTerminateEnabled(); this.isDataIsolateEnabled = configuration.isDataIsolateEnabled(); this.isDebugModeEnabled = configuration.isDebugModeEnabled(); this.isDelegatorModeEnabled = configuration.isDelegatorModeEnabled(); this.isRemoteMonitorEnabled = configuration.isRemoteMonitorEnabled(); cb.run(); prepare(); } @Override public boolean isRemoteMonitorEnabled() { return isRemoteMonitorEnabled; } protected void prepare() { if(isDebugModeEnabled) { final StateMachineLogger logger = new StateMachineLogger(this); addStartListener(new StartListener<T, S, E, C>() { @Override public void started(StartEvent<T, S, E, C> event) { logger.startLogging(); } }); addTerminateListener(new TerminateListener<T, S, E, C>() { @Override public void terminated(TerminateEvent<T, S, E, C> event) { logger.stopLogging(); } }); } } private boolean processEvent(E event, C context, StateMachineData<T, S, E, C> originalData, ActionExecutionService<T, S, E, C> executionService, boolean isDataIsolateEnabled) { StateMachineData<T, S, E, C> localData = originalData; ImmutableState<T, S, E, C> fromState = localData.read().currentRawState(); S fromStateId = fromState.getStateId(), toStateId = null; try { beforeTransitionBegin(fromStateId, event, context); fireEvent(new TransitionBeginEventImpl<T, S, E, C>(fromStateId, event, context, getThis())); if(isDataIsolateEnabled) { // use local data to isolation transition data write localData = FSM.newStateMachineData(originalData.read().originalStates()); localData.dump(originalData.read()); } TransitionResult<T, S, E, C> result = FSM.newResult(false, fromState, null); StateContext<T, S, E, C> stateContext = FSM.newStateContext(this, localData, fromState, event, context, result, executionService); fromState.internalFire(stateContext); toStateId = result.getTargetState().getStateId(); if(result.isAccepted()) { executionService.execute(); localData.write().lastState(fromStateId); localData.write().currentState(toStateId); if(isDataIsolateEnabled) { // import local data after transition accepted originalData.dump(localData.read()); } fireEvent(new TransitionCompleteEventImpl<T, S, E, C>(fromStateId, toStateId, event, context, getThis())); afterTransitionCompleted(fromStateId, getCurrentState(), event, context); return true; } else { fireEvent(new TransitionDeclinedEventImpl<T, S, E, C>(fromStateId, event, context, getThis())); afterTransitionDeclined(fromStateId, event, context); } } catch (Exception e) { // set state machine in error status first which means state machine cannot process event anymore // unless this exception has been resolved and state machine status set back to normal again. setStatus(StateMachineStatus.ERROR); // wrap any exception into transition exception lastException = (e instanceof TransitionException) ? (TransitionException) e : new TransitionException(e, ErrorCodes.FSM_TRANSITION_ERROR, new Object[]{fromStateId, toStateId, event, context, "UNKNOWN", e.getMessage()}); fireEvent(new TransitionExceptionEventImpl<T, S, E, C>(lastException, fromStateId, localData.read().currentState(), event, context, getThis())); afterTransitionCausedException(fromStateId, toStateId, event, context); } finally { executionService.reset(); fireEvent(new TransitionEndEventImpl<T, S, E, C>(fromStateId, toStateId, event, context, getThis())); afterTransitionEnd(fromStateId, getCurrentState(), event, context); } return false; } private void processEvents() { if (isIdle()) { writeLock.lock(); setStatus(StateMachineStatus.BUSY); try { Pair<E, C> eventInfo; E event; C context = null; while ((eventInfo=queuedEvents.poll())!=null) { // response to cancel operation if(Thread.interrupted()) { queuedEvents.clear(); break; } event = eventInfo.first(); context = eventInfo.second(); processEvent(event, context, data, executor, isDataIsolateEnabled); } ImmutableState<T, S, E, C> rawState = data.read().currentRawState(); if(isAutoTerminateEnabled && rawState.isRootState() && rawState.isFinalState()) { terminate(context); } } finally { if(getStatus()==StateMachineStatus.BUSY) setStatus(StateMachineStatus.IDLE); writeLock.unlock(); } } } private void internalFire(E event, C context, boolean insertAtFirst) { if(getStatus()==StateMachineStatus.INITIALIZED) { if(isAutoStartEnabled) { start(context); } else { throw new IllegalStateException("The state machine is not running."); } } if(getStatus()==StateMachineStatus.TERMINATED) { throw new IllegalStateException("The state machine is already terminated."); } if(getStatus()==StateMachineStatus.ERROR) { throw new IllegalStateException("The state machine is corruptted."); } if(insertAtFirst) { queuedEvents.addFirst(new Pair<E, C>(event, context)); } else { queuedEvents.addLast(new Pair<E, C>(event, context)); } processEvents(); } private boolean isEntryPoint() { return StateMachineContext.currentInstance()==null; } private void fire(E event, C context, boolean insertAtFirst) { boolean isEntryPoint = isEntryPoint(); if(isEntryPoint) { StateMachineContext.set(getThis()); } else if(isDelegatorModeEnabled && StateMachineContext.currentInstance()!=this) { T currentInstance = StateMachineContext.currentInstance(); currentInstance.fire(event, context); return; } try { if(StateMachineContext.isTestEvent()) { internalTest(event, context); } else { internalFire(event, context, insertAtFirst); } } finally { if(isEntryPoint) { StateMachineContext.set(null); } } } @Override public void fire(E event, C context) { fire(event, context, false); } public void untypedFire(Object event, Object context) { fire(typeOfEvent().cast(event), typeOfContext().cast(context)); } @Override public void fireImmediate(E event, C context) { fire(event, context, true); } @Override public void fire(E event) { fire(event, null); } @Override public void fireImmediate(E event) { fireImmediate(event, null); } /** * Clean all queued events */ protected void cleanQueuedEvents() { queuedEvents.clear(); } private ActionExecutionService<T, S, E, C> getDummyExecutor() { ActionExecutionService<T, S, E, C> dummyExecutor = SquirrelProvider.getInstance().newInstance( new TypeReference<ActionExecutionService<T, S, E, C>>(){}); dummyExecutor.setDummyExecution(true); return dummyExecutor; } private S internalTest(E event, C context) { checkState(status!=StateMachineStatus.ERROR && status!=StateMachineStatus.TERMINATED, "Cannot test state machine under "+status+" status."); S testResult = null; queuedTestEvents.add(new Pair<E, C>(event, context)); if(!isProcessingTestEvent) { isProcessingTestEvent = true; @SuppressWarnings("unchecked") StateMachineData<T, S, E, C> cloneData = (StateMachineData<T, S, E, C>)dumpSavedData(); ActionExecutionService<T, S, E, C> dummyExecutor = getDummyExecutor(); if(getStatus()==StateMachineStatus.INITIALIZED) { if(isAutoStartEnabled) { internalStart(context, cloneData, dummyExecutor); } else { throw new IllegalStateException("The state machine is not running."); } } try { Pair<E, C> eventInfo = null; while ((eventInfo=queuedTestEvents.poll())!=null) { E testEvent = eventInfo.first(); C testContext = eventInfo.second(); processEvent(testEvent, testContext, cloneData, dummyExecutor, false); } testResult = resolveState(cloneData.read().currentState(), cloneData); } finally { isProcessingTestEvent = false; } } return testResult; } @Override public S test(E event, C context) { boolean isEntryPoint = isEntryPoint(); if(isEntryPoint) { StateMachineContext.set(getThis(), true); } try { return internalTest(event, context); } finally { if(isEntryPoint) { StateMachineContext.set(null); } } } @Override public S test(E event) { return test(event, null); } @Override public boolean canAccept(E event) { ImmutableState<T, S, E, C> testRawState = getCurrentRawState(); if(testRawState==null) { if(isAutoStartEnabled) { testRawState = getInitialRawState(); } else { return false; } } return testRawState.getAcceptableEvents().contains(event); } protected boolean isIdle() { return getStatus()!=StateMachineStatus.BUSY; } protected void afterTransitionCausedException(S fromState, S toState, E event, C context) { if(getLastException().getTargetException()!=null) logger.error("Transition caused exception", getLastException().getTargetException()); throw getLastException(); } protected void beforeTransitionBegin(S fromState, E event, C context) { } protected void afterTransitionCompleted(S fromState, S toState, E event, C context) { } protected void afterTransitionEnd(S fromState, S toState, E event, C context) { } protected void afterTransitionDeclined(S fromState, E event, C context) { } protected void beforeActionInvoked(S fromState, S toState, E event, C context) { } protected void afterActionInvoked(S fromState, S toState, E event, C context) { } private ImmutableState<T, S, E, C> resolveRawState(ImmutableState<T, S, E, C> rawState) { ImmutableState<T, S, E, C> resolvedRawState = rawState; if(resolvedRawState instanceof ImmutableLinkedState) { @SuppressWarnings("unchecked") T linkedStateMachine = (T) ((ImmutableLinkedState<T, S, E, C>)rawState). getLinkedStateMachine(getThis()); resolvedRawState = linkedStateMachine.getCurrentRawState(); } return resolvedRawState; } @Override public ImmutableState<T, S, E, C> getCurrentRawState() { readLock.lock(); try { ImmutableState<T, S, E, C> rawState = data.read().currentRawState(); return resolveRawState(rawState); } finally { readLock.unlock(); } } @Override public ImmutableState<T, S, E, C> getLastRawState() { readLock.lock(); try { ImmutableState<T, S, E, C> lastRawState = data.read().lastRawState(); return resolveRawState(lastRawState); } finally { readLock.unlock(); } } @Override public ImmutableState<T, S, E, C> getInitialRawState() { return getRawStateFrom(getInitialState()); } @Override public ImmutableState<T, S, E, C> getRawStateFrom(S stateId) { readLock.lock(); try { return data.read().rawStateFrom(stateId); } finally { readLock.unlock(); } } @Override public Collection<ImmutableState<T, S, E, C>> getAllRawStates() { readLock.lock(); try { return data.read().rawStates(); } finally { readLock.unlock(); } } @Override public Collection<S> getAllStates() { readLock.lock(); try { return data.read().states(); } finally { readLock.unlock(); } } private S resolveState(S state, StateMachineData<T, S, E, C> localData) { S resolvedState = state; ImmutableState<T, S, E, C> rawState = localData.read().rawStateFrom(resolvedState); if(rawState instanceof ImmutableLinkedState) { ImmutableLinkedState<T, S, E, C> linkedRawState = (ImmutableLinkedState<T, S, E, C>)rawState; resolvedState = linkedRawState.getLinkedStateMachine(getThis()).getCurrentState(); } return resolvedState; } @Override public S getCurrentState() { readLock.lock(); try { return resolveState(data.read().currentState(), data); } finally { readLock.unlock(); } } @Override public S getLastState() { readLock.lock(); try { return resolveState(data.read().lastState(), data); } finally { readLock.unlock(); } } @Override public S getInitialState() { readLock.lock(); try { return data.read().initialState(); } finally { readLock.unlock(); } } private void entryAll(ImmutableState<T, S, E, C> origin, StateContext<T, S, E, C> stateContext) { Stack<ImmutableState<T, S, E, C>> stack = new Stack<ImmutableState<T, S, E, C>>(); ImmutableState<T, S, E, C> state = origin; while (state != null) { stack.push(state); state = state.getParentState(); } while (stack.size() > 0) { state = stack.pop(); state.entry(stateContext); } } @Override public synchronized void start(C context) { if(isStarted()) { return; } setStatus(StateMachineStatus.BUSY); internalStart(context, data, executor); setStatus(StateMachineStatus.IDLE); processEvents(); } private void internalStart(C context, StateMachineData<T, S, E, C> localData, ActionExecutionService<T, S, E, C> executionService) { ImmutableState<T, S, E, C> initialRawState = localData.read().initialRawState(); StateContext<T, S, E, C> stateContext = FSM.newStateContext( this, localData, initialRawState, getStartEvent(), context, null, executionService); entryAll(initialRawState, stateContext); ImmutableState<T, S, E, C> historyState = initialRawState.enterByHistory(stateContext); executionService.execute(); localData.write().currentState(historyState.getStateId()); localData.write().startContext(context); fireEvent(new StartEventImpl<T, S, E, C>(getThis())); } @Override public void start() { start(null); } @Override public boolean isStarted() { return getStatus()==StateMachineStatus.IDLE || getStatus()==StateMachineStatus.BUSY; } @Override public boolean isTerminated() { return getStatus()==StateMachineStatus.TERMINATED; } @Override public boolean isError() { return getStatus()==StateMachineStatus.ERROR; } @Override public StateMachineStatus getStatus() { return status; } protected void setStatus(StateMachineStatus status) { this.status = status; } @Override public S getLastActiveChildStateOf(S parentStateId) { readLock.lock(); try { return data.read().lastActiveChildStateOf(parentStateId); } finally { readLock.unlock(); } } @Override public List<S> getSubStatesOn(S parentStateId) { readLock.lock(); try { return data.read().subStatesOn(parentStateId); } finally { readLock.unlock(); } } @Override public synchronized void terminate(C context) { if(isTerminated()) { return; } StateContext<T, S, E, C> stateContext = FSM.newStateContext( this, data, data.read().currentRawState(), getTerminateEvent(), context, null, executor); exitAll(data.read().currentRawState(), stateContext); executor.execute(); setStatus(StateMachineStatus.TERMINATED); fireEvent(new TerminateEventImpl<T, S, E, C>(getThis())); } @Override public void terminate() { terminate(null); } private void exitAll(ImmutableState<T, S, E, C> current, StateContext<T, S, E, C> stateContext) { ImmutableState<T, S, E, C> state = current; while (state != null) { state.exit(stateContext); state = state.getParentState(); } } @SuppressWarnings("unchecked") @Override public T getThis() { return (T)this; } @Override public void accept(Visitor visitor) { visitor.visitOnEntry(this); for(ImmutableState<T, S, E, C> state : getAllRawStates()) { if(state.getParentState()==null) state.accept(visitor); } visitor.visitOnExit(this); } void setTypeOfStateMachine(Class<? extends T> stateMachineType) { data.write().typeOfStateMachine(stateMachineType); } void setTypeOfState(Class<S> stateType) { data.write().typeOfState(stateType); } void setTypeOfEvent(Class<E> eventType) { data.write().typeOfEvent(eventType); } void setTypeOfContext(Class<C> contextType) { data.write().typeOfContext(contextType); } void setScriptManager(MvelScriptManager scriptManager) { checkState(this.scriptManager==null); this.scriptManager = scriptManager; } void setStartEvent(E startEvent) { checkState(this.startEvent==null); this.startEvent=startEvent; } E getStartEvent() { return startEvent; } void setTerminateEvent(E terminateEvent) { checkState(this.terminateEvent==null); this.terminateEvent=terminateEvent; } E getTerminateEvent() { return terminateEvent; } void setFinishEvent(E finishEvent) { checkState(this.finishEvent==null); this.finishEvent=finishEvent; } E getFinishEvent() { return finishEvent; } void setExtraParamTypes(Class<?>[] extraParamTypes) { checkState(this.extraParamTypes==null); this.extraParamTypes = extraParamTypes; } @Override public StateMachineData.Reader<T, S, E, C> dumpSavedData() { readLock.lock(); try { StateMachineData<T, S, E, C> savedData = FSM.newStateMachineData(data.read().originalStates()); savedData.dump(data.read()); // process linked state if any saveLinkedStateData(data.read(), savedData.write()); return savedData.read(); } finally { readLock.unlock(); } } private void saveLinkedStateData(StateMachineData.Reader<T, S, E, C> src, StateMachineData.Writer<T, S, E, C> target) { dumpLinkedStateFor(src.currentRawState(), target); // dumpLinkedStateFor(src.lastRawState(), target); // TODO-hhe: dump linked state info for last active child state // TODO-hhe: dump linked state info for parallel state } private void dumpLinkedStateFor(ImmutableState<T, S, E, C> rawState, StateMachineData.Writer<T, S, E, C> target) { if(rawState!=null && rawState instanceof ImmutableLinkedState) { ImmutableLinkedState<T, S, E, C> linkedRawState = (ImmutableLinkedState<T, S, E, C>)rawState; StateMachineData.Reader<? extends StateMachine<?, S, E, C>, S, E, C> linkStateData = linkedRawState.getLinkedStateMachine(getThis()).dumpSavedData(); target.linkedStateDataOn(rawState.getStateId(), linkStateData); } } @SuppressWarnings({"unchecked", "rawtypes"}) @Override public boolean loadSavedData(StateMachineData.Reader<T, S, E, C> savedData) { Preconditions.checkNotNull(savedData, "Saved data cannot be null"); if(writeLock.tryLock()) { try { data.dump(savedData); // process linked state if any for(S linkedState : savedData.linkedStates()) { StateMachineData.Reader linkedStateData = savedData.linkedStateDataOf(linkedState); ImmutableState<T, S, E, C> rawState = data.read().rawStateFrom(linkedState); if(linkedStateData!=null && rawState instanceof ImmutableLinkedState) { ImmutableLinkedState<T, S, E, C> linkedRawState = (ImmutableLinkedState<T, S, E, C>)rawState; linkedRawState.getLinkedStateMachine(getThis()).loadSavedData(linkedStateData); } } setStatus(StateMachineStatus.IDLE); return true; } finally { writeLock.unlock(); } } return false; } @Override public boolean isContextSensitive() { return true; } @Override public Class<C> typeOfContext() { return data.read().typeOfContext(); } @Override public Class<E> typeOfEvent() { return data.read().typeOfEvent(); } @Override public Class<S> typeOfState() { return data.read().typeOfState(); } @Override public TransitionException getLastException() { return lastException; } protected void setLastException(TransitionException lastException) { this.lastException = lastException; } private interface DeclarativeListener { Object getListenTarget(); } private Object newListenerMethodProxy(final Object listenTarget, final Method listenerMethod, final Class<?> listenerInterface, final String condition) { final String listenerMethodName = ReflectUtils.getStatic( ReflectUtils.getField(listenerInterface, "METHOD_NAME")).toString(); AsyncExecute asyncAnnotation = ReflectUtils.getAnnotation(listenTarget.getClass(), AsyncExecute.class); if(asyncAnnotation==null) { asyncAnnotation = listenerMethod.getAnnotation(AsyncExecute.class); } final boolean isAsync = asyncAnnotation!=null; final long timeout = asyncAnnotation!=null ? asyncAnnotation.timeout() : -1; InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("getListenTarget")) { return listenTarget; } else if(method.getName().equals(listenerMethodName)) { if(args[0] instanceof TransitionEvent) { @SuppressWarnings("unchecked") TransitionEvent<T, S, E, C> event = (TransitionEvent<T, S, E, C>)args[0]; return invokeTransitionListenerMethod(listenTarget, listenerMethod, condition, event); } else if(args[0] instanceof ActionEvent) { @SuppressWarnings("unchecked") ActionEvent<T, S, E, C> event = (ActionEvent<T, S, E, C>)args[0]; return invokeActionListenerMethod(listenTarget, listenerMethod, condition, event); } else if(args[0] instanceof StartEvent || args[0] instanceof TerminateEvent) { @SuppressWarnings("unchecked") StateMachineEvent<T, S, E, C> event = (StateMachineEvent<T, S, E, C>)args[0]; return invokeStateMachineListenerMethod(listenTarget, listenerMethod, condition, event); } else { throw new IllegalArgumentException("Unable to recognize argument type "+args[0].getClass().getName()+"."); } } else if(method.getName().equals("equals")) { return super.equals(args[0]); } else if(method.getName().equals("hashCode")) { return super.hashCode(); } else if(method.getName().equals("toString")) { return super.toString(); } else if(isAsync && method.getName().equals("timeout")) { return timeout; } throw new UnsupportedOperationException("Cannot invoke method "+method.getName()+"."); } }; Class<?>[] implementedInterfaces = isAsync ? new Class<?>[]{listenerInterface, DeclarativeListener.class, AsyncEventListener.class} : new Class<?>[]{listenerInterface, DeclarativeListener.class}; Object proxyListener = Proxy.newProxyInstance(StateMachine.class.getClassLoader(), implementedInterfaces, invocationHandler); return proxyListener; } private Object invokeStateMachineListenerMethod(final Object listenTarget, final Method listenerMethod, final String condition, final StateMachineEvent<T, S, E, C> event) { Class<?>[] parameterTypes = listenerMethod.getParameterTypes(); final Map<String, Object> variables = Maps.newHashMap(); variables.put(MvelScriptManager.VAR_STATE_MACHINE, event.getStateMachine()); boolean isSatisfied = true; if(condition!=null && condition.length()>0) { isSatisfied = scriptManager.evalBoolean(condition, variables); } if(!isSatisfied) return null; if(parameterTypes.length == 0) { return ReflectUtils.invoke(listenerMethod, listenTarget); } // parameter values infer List<Object> parameterValues = Lists.newArrayList(); for(Class<?> parameterType : parameterTypes) { if(parameterType.isAssignableFrom(AbstractStateMachine.this.getClass())) { parameterValues.add(event.getStateMachine()); } else { parameterValues.add(null); } } return ReflectUtils.invoke(listenerMethod, listenTarget, parameterValues.toArray()); } private Object invokeActionListenerMethod(final Object listenTarget, final Method listenerMethod, final String condition, final ActionEvent<T, S, E, C> event) { Class<?>[] parameterTypes = listenerMethod.getParameterTypes(); final Map<String, Object> variables = Maps.newHashMap(); variables.put(MvelScriptManager.VAR_FROM, event.getFrom()); variables.put(MvelScriptManager.VAR_TO, event.getTo()); variables.put(MvelScriptManager.VAR_EVENT, event.getEvent()); variables.put(MvelScriptManager.VAR_CONTEXT, event.getContext()); variables.put(MvelScriptManager.VAR_STATE_MACHINE, event.getStateMachine()); if(event instanceof ExecActionExceptionEvent) { Exception e = ((ExecActionExceptionEvent<T, S, E, C>)event).getException(); variables.put(MvelScriptManager.VAR_EXCEPTION, e); } boolean isSatisfied = true; if(condition!=null && condition.length()>0) { isSatisfied = scriptManager.evalBoolean(condition, variables); } if(!isSatisfied) return null; if(parameterTypes.length == 0) { return ReflectUtils.invoke(listenerMethod, listenTarget); } // parameter values infer List<Object> parameterValues = Lists.newArrayList(); boolean isSourceStateSet = false, isTargetStateSet=false, isEventSet=false, isContextSet=false; for(Class<?> parameterType : parameterTypes) { if(!isSourceStateSet && parameterType.isAssignableFrom(typeOfState())) { parameterValues.add(event.getFrom()); isSourceStateSet = true; } else if(!isTargetStateSet && parameterType.isAssignableFrom(typeOfState())) { parameterValues.add(event.getTo()); isTargetStateSet = true; } else if(!isEventSet && parameterType.isAssignableFrom(typeOfEvent())) { parameterValues.add(event.getEvent()); isEventSet = true; } else if(!isContextSet && parameterType.isAssignableFrom(typeOfContext())) { parameterValues.add(event.getContext()); isContextSet = true; } else if(parameterType.isAssignableFrom(AbstractStateMachine.this.getClass())) { parameterValues.add(event.getStateMachine()); } else if(parameterType.isAssignableFrom(Action.class)) { parameterValues.add(event.getExecutionTarget()); } else if(parameterType==int[].class) { parameterValues.add(event.getMOfN()); } else if(event instanceof ExecActionExceptionEvent && parameterType.isAssignableFrom(TransitionException.class)) { parameterValues.add(((ExecActionExceptionEvent<T, S, E, C>)event).getException()); } else { parameterValues.add(null); } } return ReflectUtils.invoke(listenerMethod, listenTarget, parameterValues.toArray()); } private Object invokeTransitionListenerMethod(final Object listenTarget, final Method listenerMethod, final String condition, final TransitionEvent<T, S, E, C> event) { Class<?>[] parameterTypes = listenerMethod.getParameterTypes(); final Map<String, Object> variables = Maps.newHashMap(); variables.put(MvelScriptManager.VAR_FROM, event.getSourceState()); variables.put(MvelScriptManager.VAR_EVENT, event.getCause()); variables.put(MvelScriptManager.VAR_CONTEXT, event.getContext()); variables.put(MvelScriptManager.VAR_STATE_MACHINE, event.getStateMachine()); if(event instanceof TransitionCompleteEvent) { variables.put(MvelScriptManager.VAR_TO, ((TransitionCompleteEvent<T, S, E, C>)event).getTargetState()); } else if(event instanceof TransitionExceptionEvent) { variables.put(MvelScriptManager.VAR_TO, ((TransitionExceptionEvent<T, S, E, C>)event).getTargetState()); variables.put(MvelScriptManager.VAR_EXCEPTION, ((TransitionExceptionEvent<T, S, E, C>)event).getException()); } boolean isSatisfied = true; if(condition!=null && condition.length()>0) { isSatisfied = scriptManager.evalBoolean(condition, variables); } if(!isSatisfied) return null; if(parameterTypes.length == 0) { return ReflectUtils.invoke(listenerMethod, listenTarget); } // parameter values infer List<Object> parameterValues = Lists.newArrayList(); boolean isSourceStateSet = false, isTargetStateSet=false, isEventSet=false, isContextSet=false; for(Class<?> parameterType : parameterTypes) { if(!isSourceStateSet && parameterType.isAssignableFrom(typeOfState())) { parameterValues.add(event.getSourceState()); isSourceStateSet = true; } else if(!isTargetStateSet && event instanceof TransitionEndEvent && parameterType.isAssignableFrom(typeOfState())) { parameterValues.add(((TransitionEndEvent<T, S, E, C>)event).getTargetState()); isTargetStateSet = true; } else if(!isTargetStateSet && event instanceof TransitionCompleteEvent && parameterType.isAssignableFrom(typeOfState())) { parameterValues.add(((TransitionCompleteEvent<T, S, E, C>)event).getTargetState()); isTargetStateSet = true; } else if(!isTargetStateSet && event instanceof TransitionExceptionEvent && parameterType.isAssignableFrom(typeOfState())) { parameterValues.add(((TransitionExceptionEvent<T, S, E, C>)event).getTargetState()); isTargetStateSet = true; } else if(!isEventSet && parameterType.isAssignableFrom(typeOfEvent())) { parameterValues.add(event.getCause()); isEventSet = true; } else if(!isContextSet && parameterType.isAssignableFrom(typeOfContext())) { parameterValues.add(event.getContext()); isContextSet = true; } else if(parameterType.isAssignableFrom(AbstractStateMachine.this.getClass())) { parameterValues.add(event.getStateMachine()); } else if(event instanceof TransitionExceptionEvent && parameterType.isAssignableFrom(TransitionException.class)) { parameterValues.add(((TransitionExceptionEvent<T, S, E, C>)event).getException()); } else { parameterValues.add(null); } } return ReflectUtils.invoke(listenerMethod, listenTarget, parameterValues.toArray()); } private void registerDeclarativeListener(final Object listenerMethodProvider, Method listenerMethod, Observable listenTarget, Class<? extends Annotation> annotationClass, Class<?> listenerClass, Class<?> eventClass) { Annotation anno = listenerMethod.getAnnotation(annotationClass); if(anno!=null) { Method whenMethod = ReflectUtils.getMethod(anno.getClass(), "when", new Class[0]); String whenCondition = StringUtils.EMPTY; if(whenMethod!=null) { whenCondition = (String)ReflectUtils.invoke(whenMethod, anno); } Field methodField = ReflectUtils.getField(listenerClass, "METHOD"); if(methodField!=null && Modifier.isStatic(methodField.getModifiers())) { Method method = (Method)ReflectUtils.getStatic(methodField); Object proxyListener = newListenerMethodProxy(listenerMethodProvider, listenerMethod, listenerClass, whenCondition); listenTarget.addListener(eventClass, proxyListener, method); } else { logger.info("Cannot find static field named 'METHOD' on listener class '"+listenerClass+"'."); } } } private static final Class<?>[][] stateMachineListenerMapping = { {OnTransitionBegin.class, TransitionBeginListener.class, TransitionBeginEvent.class}, {OnTransitionComplete.class, TransitionCompleteListener.class, TransitionCompleteEvent.class}, {OnTransitionDecline.class, TransitionDeclinedListener.class, TransitionDeclinedEvent.class}, {OnTransitionEnd.class, TransitionEndListener.class, TransitionEndEvent.class}, {OnTransitionException.class, TransitionExceptionListener.class, TransitionExceptionEvent.class}, {OnStateMachineStart.class, StartListener.class, StartEvent.class}, {OnStateMachineTerminate.class, TerminateListener.class, TerminateEvent.class} }; private static final Class<?>[][] actionExecutorListenerMapping = { {OnBeforeActionExecuted.class, BeforeExecActionListener.class, BeforeExecActionEvent.class}, {OnAfterActionExecuted.class, AfterExecActionListener.class, AfterExecActionEvent.class}, {OnActionExecException.class, ExecActionExceptionListener.class, ExecActionExceptionEvent.class}, }; @Override @SuppressWarnings("unchecked") public void addDeclarativeListener(final Object listenerMethodProvider) { // If no declarative listener was register, please make sure your listener was public method List<String> visitedMethods = Lists.newArrayList(); Method[] methods = listenerMethodProvider.getClass().getMethods(); Arrays.sort(methods, new Comparator<Method>() { @Override public int compare(Method o1, Method o2) { ListenerOrder or1 = o1.getAnnotation(ListenerOrder.class); ListenerOrder or2 = o2.getAnnotation(ListenerOrder.class); // nulls last if (or1 != null && or2 != null) { return or1.value() - or2.value(); } else if (or1 != null && or2 == null) { return -1; } else if (or1 == null && or2 != null) { return 1; } return o1.getName().compareTo(o2.getName()); } }); for(final Method listenerMethod : methods) { if("--wait-notify-notifyAll-toString-equals-hashCode-getClass--". indexOf("-"+listenerMethod.getName()+"-")>0) continue; String methodSignature = listenerMethod.toString(); if(visitedMethods.contains(methodSignature)) continue; visitedMethods.add(methodSignature); for(int i=0; i<stateMachineListenerMapping.length; ++i) { registerDeclarativeListener(listenerMethodProvider, listenerMethod, this, (Class<? extends Annotation>)stateMachineListenerMapping[i][0], stateMachineListenerMapping[i][1], stateMachineListenerMapping[i][2]); } for(int i=0; i<actionExecutorListenerMapping.length; ++i) { registerDeclarativeListener(listenerMethodProvider, listenerMethod, executor, (Class<? extends Annotation>)actionExecutorListenerMapping[i][0], actionExecutorListenerMapping[i][1], actionExecutorListenerMapping[i][2]); } } } @Override public void removeDeclarativeListener(final Object listenerMethodProvider) { removeDeclarativeListener(this, listenerMethodProvider); removeDeclarativeListener(executor, listenerMethodProvider); } /** * Internal use only * @return ActionExecutionService */ public int getExecutorListenerSize() { return executor.getListenerSize(); } @Override public String getIdentifier() { return data.read().identifier(); } @Override public String getDescription() { StringBuilder builder = new StringBuilder(); builder.append("id=\"").append(getIdentifier()).append("\" "); builder.append("fsm-type=\"").append(getClass().getName()).append("\" "); builder.append("state-type=\"").append(typeOfState().getName()).append("\" "); builder.append("event-type=\"").append(typeOfEvent().getName()).append("\" "); builder.append("context-type=\"").append(typeOfContext().getName()).append("\" "); Converter<E> eventConverter = ConverterProvider.INSTANCE.getConverter(typeOfEvent()); if(getStartEvent()!=null) { builder.append("start-event=\""); builder.append(eventConverter.convertToString(getStartEvent())); builder.append("\" "); } if(getTerminateEvent()!=null) { builder.append("terminate-event=\""); builder.append(eventConverter.convertToString(getTerminateEvent())); builder.append("\" "); } if(getFinishEvent()!=null) { builder.append("finish-event=\""); builder.append(eventConverter.convertToString(getFinishEvent())); builder.append("\" "); } builder.append("context-insensitive=\"").append(isContextSensitive()).append("\" "); if(extraParamTypes!=null && extraParamTypes.length>0) { builder.append("extra-parameters=\"["); for(int i=0; i<extraParamTypes.length; ++i) { if(i>0) builder.append(","); builder.append(extraParamTypes[i].getName()); } builder.append("]\" "); } return builder.toString(); } @Override public String exportXMLDefinition(boolean beautifyXml) { SCXMLVisitor visitor = SquirrelProvider.getInstance().newInstance(SCXMLVisitor.class); accept(visitor); return visitor.getScxml(beautifyXml); } private void removeDeclarativeListener(Observable observable, final Object listenTarget) { observable.removeListener(new Predicate<ListenerMethod>() { @Override public boolean apply(ListenerMethod input) { return (input.getTarget() instanceof DeclarativeListener) && ((DeclarativeListener)input.getTarget()).getListenTarget()==listenTarget; } }); } @Override public void addStateMachineListener(StateMachineListener<T, S, E, C> listener) { addListener(StateMachineEvent.class, listener, StateMachineListener.METHOD); } @Override public void removeStateMachineListener(StateMachineListener<T, S, E, C> listener) { removeListener(StateMachineEvent.class, listener, StateMachineListener.METHOD); } @Override public void addStartListener(StartListener<T, S, E, C> listener) { addListener(StartEvent.class, listener, StartListener.METHOD); } @Override public void removeStartListener(StartListener<T, S, E, C> listener) { removeListener(StartEvent.class, listener, StartListener.METHOD); } @Override public void addTerminateListener(TerminateListener<T, S, E, C> listener) { addListener(TerminateEvent.class, listener, TerminateListener.METHOD); } @Override public void removeTerminateListener(TerminateListener<T, S, E, C> listener) { removeListener(TerminateEvent.class, listener, TerminateListener.METHOD); } @Override public void addStateMachineExceptionListener(StateMachineExceptionListener<T, S, E, C> listener) { addListener(StateMachineExceptionEvent.class, listener, StateMachineExceptionListener.METHOD); } @Override public void removeStateMachineExceptionListener(StateMachineExceptionListener<T, S, E, C> listener) { removeListener(StateMachineExceptionEvent.class, listener, StateMachineExceptionListener.METHOD); } @Override public void addTransitionBeginListener(TransitionBeginListener<T, S, E, C> listener) { addListener(TransitionBeginEvent.class, listener, TransitionBeginListener.METHOD); } @Override public void removeTransitionBeginListener(TransitionBeginListener<T, S, E, C> listener) { removeListener(TransitionBeginEvent.class, listener, TransitionBeginListener.METHOD); } @Override public void addTransitionCompleteListener(TransitionCompleteListener<T, S, E, C> listener) { addListener(TransitionCompleteEvent.class, listener, TransitionCompleteListener.METHOD); } @Override public void removeTransitionCompleteListener(TransitionCompleteListener<T, S, E, C> listener) { removeListener(TransitionCompleteEvent.class, listener, TransitionCompleteListener.METHOD); } @Override public void addTransitionExceptionListener(TransitionExceptionListener<T, S, E, C> listener) { addListener(TransitionExceptionEvent.class, listener, TransitionExceptionListener.METHOD); } @Override public void removeTransitionExceptionListener(TransitionExceptionListener<T, S, E, C> listener) { removeListener(TransitionExceptionEvent.class, listener, TransitionExceptionListener.METHOD); } @Override public void addTransitionDeclinedListener(TransitionDeclinedListener<T, S, E, C> listener) { addListener(TransitionDeclinedEvent.class, listener, TransitionDeclinedListener.METHOD); } @Override public void removeTransitionDeclinedListener(TransitionDeclinedListener<T, S, E, C> listener) { removeListener(TransitionDeclinedEvent.class, listener, TransitionDeclinedListener.METHOD); } @Override @Deprecated public void removeTransitionDecleindListener(TransitionDeclinedListener<T, S, E, C> listener) { removeListener(TransitionDeclinedEvent.class, listener, TransitionDeclinedListener.METHOD); } @Override public void addTransitionEndListener(TransitionEndListener<T, S, E, C> listener) { addListener(TransitionEndEvent.class, listener, TransitionEndListener.METHOD); } @Override public void removeTransitionEndListener(TransitionEndListener<T, S, E, C> listener) { removeListener(TransitionEndEvent.class, listener, TransitionEndListener.METHOD); } @Override public void addExecActionListener(BeforeExecActionListener<T, S, E, C> listener) { executor.addExecActionListener(listener); } @Override public void removeExecActionListener(BeforeExecActionListener<T, S, E, C> listener) { executor.removeExecActionListener(listener); } public static abstract class AbstractStateMachineEvent<T extends StateMachine<T, S, E, C>, S, E, C> implements StateMachine.StateMachineEvent<T, S, E, C> { private final T stateMachine; public AbstractStateMachineEvent(T stateMachine) { this.stateMachine = stateMachine; } @Override public T getStateMachine() { return stateMachine; } } public static class StartEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractStateMachineEvent<T, S, E, C> implements StateMachine.StartEvent<T, S, E, C> { public StartEventImpl(T source) { super(source); } } public static class TerminateEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractStateMachineEvent<T, S, E, C> implements StateMachine.TerminateEvent<T, S, E, C> { public TerminateEventImpl(T source) { super(source); } } public static class StateMachineExceptionEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractStateMachineEvent<T, S, E, C> implements StateMachine.StateMachineExceptionEvent<T, S, E, C> { private final Exception e; public StateMachineExceptionEventImpl(Exception e, T source) { super(source); this.e = e; } @Override public Exception getException() { return e; } } public static abstract class AbstractTransitionEvent<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractStateMachineEvent<T, S, E, C> implements StateMachine.TransitionEvent<T, S, E, C> { private final S sourceState; private final E event; private final C context; public AbstractTransitionEvent(S sourceState, E event, C context, T stateMachine) { super(stateMachine); this.sourceState = sourceState; this.event = event; this.context = context; } @Override public S getSourceState() { return sourceState; } @Override public E getCause() { return event; } @Override public C getContext() { return context; } } public static class TransitionBeginEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractTransitionEvent<T, S, E, C> implements StateMachine.TransitionBeginEvent<T, S, E, C> { public TransitionBeginEventImpl(S sourceState, E event, C context,T stateMachine) { super(sourceState, event, context, stateMachine); } } public static class TransitionCompleteEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractTransitionEvent<T, S, E, C> implements StateMachine.TransitionCompleteEvent<T, S, E, C> { private final S targetState; public TransitionCompleteEventImpl(S sourceState, S targetState, E event, C context,T stateMachine) { super(sourceState, event, context, stateMachine); this.targetState = targetState; } @Override public S getTargetState() { return targetState; } } public static class TransitionExceptionEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractTransitionEvent<T, S, E, C> implements StateMachine.TransitionExceptionEvent<T, S, E, C> { private final S targetState; private final TransitionException e; public TransitionExceptionEventImpl(TransitionException e, S sourceState, S targetState, E event, C context,T stateMachine) { super(sourceState, event, context, stateMachine); this.targetState = targetState; this.e = e; } @Override public S getTargetState() { return targetState; } @Override public TransitionException getException() { return e; } } public static class TransitionDeclinedEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractTransitionEvent<T, S, E, C> implements StateMachine.TransitionDeclinedEvent<T, S, E, C> { public TransitionDeclinedEventImpl(S sourceState, E event, C context,T stateMachine) { super(sourceState, event, context, stateMachine); } } public static class TransitionEndEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractTransitionEvent<T, S, E, C> implements StateMachine.TransitionEndEvent<T, S, E, C> { private final S targetState; public TransitionEndEventImpl(S sourceState, S targetState, E event, C context,T stateMachine) { super(sourceState, event, context, stateMachine); this.targetState = targetState; } @Override public S getTargetState() { return targetState; } } }