package org.squirrelframework.foundation.fsm.impl; import static com.google.common.base.Preconditions.checkNotNull; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.squirrelframework.foundation.component.SquirrelConfiguration; import org.squirrelframework.foundation.component.impl.AbstractSubject; import org.squirrelframework.foundation.exception.ErrorCodes; import org.squirrelframework.foundation.exception.SquirrelRuntimeException; import org.squirrelframework.foundation.exception.TransitionException; import org.squirrelframework.foundation.fsm.Action; import org.squirrelframework.foundation.fsm.ActionExecutionService; import org.squirrelframework.foundation.fsm.StateMachine; import org.squirrelframework.foundation.fsm.StateMachineContext; import org.squirrelframework.foundation.util.Pair; import com.google.common.collect.Maps; public abstract class AbstractExecutionService<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractSubject implements ActionExecutionService<T, S, E, C> { private static final Logger logger = LoggerFactory.getLogger(AbstractExecutionService.class); protected final LinkedList<Pair<String, List<ActionContext<T, S, E, C>>>> actionBuckets = new LinkedList<Pair<String, List<ActionContext<T, S, E, C>>>>(); protected boolean dummyExecution = false; private int actionTotalSize = 0; @Override public void begin(String bucketName) { List<ActionContext<T, S, E, C>> actionContext = new ArrayList<ActionContext<T, S, E, C>>(); actionBuckets.add(new Pair<String, List<ActionContext<T, S, E, C>>>(bucketName, actionContext)); } @Override public void defer(Action<T, S, E, C> action, S from, S to, E event, C context, T stateMachine) { checkNotNull(action, "Action parameter cannot be null."); List<ActionContext<T, S, E, C>> actions = actionBuckets.peekLast().second(); checkNotNull(actions, "Action bucket currently is empty. Make sure execution service is began."); actions.add(ActionContext.get(action, from, to, event, context, stateMachine, ++actionTotalSize)); } private void doExecute(String bucketName, List<ActionContext<T, S, E, C>> bucketActions) { checkNotNull(bucketActions, "Action bucket cannot be empty when executing."); final Map<ActionContext<T, S, E, C>, Future<?>> futures = Maps.newHashMap(); for (int i=0, actionSize = bucketActions.size(); i<actionSize; ++i) { final ActionContext<T, S, E, C> actionContext = bucketActions.get(i); if(actionContext.action.weight()!=Action.IGNORE_WEIGHT) { try { fireEvent(BeforeExecActionEventImpl.get(actionContext.position, actionTotalSize, actionContext)); if(dummyExecution) continue; if(actionContext.action.isAsync()) { final boolean isTestEvent = StateMachineContext.isTestEvent(); final T instance = StateMachineContext.currentInstance(); Future<?> future = SquirrelConfiguration.getExecutor().submit(new Runnable() { @Override public void run() { StateMachineContext.set(instance, isTestEvent); try { actionContext.run(); } finally { StateMachineContext.set(null); } } }); // if run background then not add to this list futures.put(actionContext, future); } else { actionContext.run(); } } catch (Exception e) { logger.error("Error during transition", e); Throwable t = (e instanceof SquirrelRuntimeException) ? ((SquirrelRuntimeException)e).getTargetException() : e; // wrap any exception into transition exception TransitionException te = new TransitionException(t, ErrorCodes.FSM_TRANSITION_ERROR, new Object[]{actionContext.from, actionContext.to, actionContext.event, actionContext.context, actionContext.action.name(), e.getMessage()}); fireEvent(new ExecActionExceptionEventImpl<T, S, E, C>(te, i+1, actionSize, actionContext)); throw te; } finally { fireEvent(AfterExecActionEventImpl.get(i+1, actionSize, actionContext)); } } else { logger.info("Method call action \""+actionContext.action.name()+"\" ("+(i+1)+" of "+actionSize+") was ignored."); } } for(Entry<ActionContext<T, S, E, C>, Future<?>> entry : futures.entrySet()) { final Future<?> future = entry.getValue(); final ActionContext<T, S, E, C> actionContext = entry.getKey(); try { logger.debug("Waiting action \'"+actionContext.action.toString()+"\' to finish."); if(actionContext.action.timeout()>=0) { future.get(actionContext.action.timeout(), TimeUnit.MILLISECONDS); } else { future.get(); } logger.debug("Action \'"+actionContext.action.toString()+"\' finished."); } catch (Exception e) { future.cancel(true); Throwable t = e; if(e instanceof ExecutionException) { t = ((ExecutionException)e).getCause(); } TransitionException te = new TransitionException(t, ErrorCodes.FSM_TRANSITION_ERROR, new Object[]{actionContext.from, actionContext.to, actionContext.event, actionContext.context, actionContext.action.name(), e.getMessage()}); fireEvent(new ExecActionExceptionEventImpl<T, S, E, C>(te, actionContext.position, actionTotalSize, actionContext)); throw te; } } } private void executeActions() { Pair<String, List<ActionContext<T, S, E, C>>> actionBucket = actionBuckets.poll(); String bucketName = actionBucket.first(); List<ActionContext<T, S, E, C>> actionContexts = actionBucket.second(); doExecute(bucketName, actionContexts); logger.debug("Actions within \'"+bucketName+"' invoked."); } @Override public void execute() { try { while(actionBuckets.size()>0) { executeActions(); } } finally { reset(); } } @Override public void reset() { actionBuckets.clear(); actionTotalSize = 0; } @Override public void addExecActionListener(BeforeExecActionListener<T, S, E, C> listener) { addListener(BeforeExecActionEvent.class, listener, BeforeExecActionListener.METHOD); } @Override public void removeExecActionListener(BeforeExecActionListener<T, S, E, C> listener) { removeListener(BeforeExecActionEvent.class, listener); } @Override public void addExecActionListener(AfterExecActionListener<T, S, E, C> listener) { addListener(AfterExecActionListener.class, listener, AfterExecActionListener.METHOD); } @Override public void removeExecActionListener(AfterExecActionListener<T, S, E, C> listener) { removeListener(AfterExecActionListener.class, listener); } @Override public void addExecActionExceptionListener(ExecActionExceptionListener<T, S, E, C> listener) { addListener(ExecActionExceptionEvent.class, listener, ExecActionExceptionListener.METHOD); } @Override public void removeExecActionExceptionListener(ExecActionExceptionListener<T, S, E, C> listener) { removeListener(ExecActionExceptionEvent.class, listener); } @Override public void setDummyExecution(boolean dummyExecution) { this.dummyExecution = dummyExecution; } static class ExecActionExceptionEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractExecActionEvent<T, S, E, C> implements ExecActionExceptionEvent<T, S, E, C> { private final TransitionException e; ExecActionExceptionEventImpl(TransitionException e, int pos, int size, ActionContext<T, S, E, C> actionContext) { super(pos, size, actionContext); this.e = e; } @Override public TransitionException getException() { return e; } } static class BeforeExecActionEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractExecActionEvent<T, S, E, C> implements BeforeExecActionEvent<T, S, E, C> { BeforeExecActionEventImpl(int pos, int size, ActionContext<T, S, E, C> actionContext) { super(pos, size, actionContext); } static <T extends StateMachine<T, S, E, C>, S, E, C> BeforeExecActionEvent<T, S, E, C> get( int pos, int size, ActionContext<T, S, E, C> actionContext) { return new BeforeExecActionEventImpl<T, S, E, C>(pos, size, actionContext); } } static class AfterExecActionEventImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends AbstractExecActionEvent<T, S, E, C> implements AfterExecActionEvent<T, S, E, C> { AfterExecActionEventImpl(int pos, int size, ActionContext<T, S, E, C> actionContext) { super(pos, size, actionContext); } static <T extends StateMachine<T, S, E, C>, S, E, C> AfterExecActionEvent<T, S, E, C> get( int pos, int size, ActionContext<T, S, E, C> actionContext) { return new AfterExecActionEventImpl<T, S, E, C>(pos, size, actionContext); } } static abstract class AbstractExecActionEvent<T extends StateMachine<T, S, E, C>, S, E, C> implements ActionEvent<T, S, E, C> { private ActionContext<T, S, E, C> executionContext; private int pos; private int size; AbstractExecActionEvent(int pos, int size, ActionContext<T, S, E, C> actionContext) { this.pos = pos; this.size = size; this.executionContext = actionContext; } @Override public Action<T, S, E, C> getExecutionTarget() { // user can only read action info but cannot invoke action in the listener method return new UncallableActionImpl<T, S, E, C>(executionContext.action); } @Override public S getFrom() { return executionContext.from; } @Override public S getTo() { return executionContext.to; } @Override public E getEvent() { return executionContext.event; } @Override public C getContext() { return executionContext.context; } @Override public T getStateMachine() { return executionContext.fsm; } @Override public int[] getMOfN() { return new int[]{pos, size}; } } static class ActionContext<T extends StateMachine<T, S, E, C>, S, E, C> { final Action<T, S, E, C> action; final S from; final S to; final E event; final C context; final T fsm; final int position; private ActionContext(Action<T, S, E, C> action, S from, S to, E event, C context, T stateMachine, int position) { this.action = action; this.from = from; this.to = to; this.event = event; this.context = context; this.fsm = stateMachine; this.position = position; } static <T extends StateMachine<T, S, E, C>, S, E, C> ActionContext<T, S, E, C> get( Action<T, S, E, C> action, S from, S to, E event, C context, T stateMachine, int position) { return new ActionContext<T, S, E, C>(action, from, to, event, context, stateMachine, position); } void run() { AbstractStateMachine<T, S, E, C> fsmImpl = (AbstractStateMachine<T, S, E, C>)fsm; fsmImpl.beforeActionInvoked(from, to, event, context); action.execute(from, to, event, context, fsm); fsmImpl.afterActionInvoked(from, to, event, context); } } }