All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.squirrelframework.foundation.fsm.impl.AbstractExecutionService Maven / Gradle / Ivy

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, S, E, C> 
    extends AbstractSubject implements ActionExecutionService {
    
    private static final Logger logger = LoggerFactory.getLogger(AbstractExecutionService.class);

    protected final LinkedList>>> actionBuckets = 
            new LinkedList>>>();
    
    protected boolean dummyExecution = false;
    
    private int actionTotalSize = 0;
    
    @Override
    public void begin(String bucketName) {
        List> actionContext = new ArrayList>();
        actionBuckets.add(new Pair>>(bucketName, actionContext));
    }
    
    @Override
    public void defer(Action action, S from, S to, E event, C context, T stateMachine) {
        checkNotNull(action, "Action parameter cannot be null.");
        List> 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> bucketActions) {
        checkNotNull(bucketActions, "Action bucket cannot be empty when executing.");
        final Map, Future> futures = Maps.newHashMap();
        for (int i=0, actionSize = bucketActions.size(); i 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(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, Future> entry : futures.entrySet()) {
            final Future future = entry.getValue();
            final ActionContext 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(te, 
                        actionContext.position, actionTotalSize, actionContext));
                throw te;
            }
        }
    }
    
    private void executeActions() {
        Pair>> actionBucket = actionBuckets.poll();
        String bucketName = actionBucket.first();
        List> 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 listener) {
        addListener(BeforeExecActionEvent.class, listener, BeforeExecActionListener.METHOD);
    }
    
    @Override
    public void removeExecActionListener(BeforeExecActionListener listener) {
        removeListener(BeforeExecActionEvent.class, listener);
    }
    
    @Override
    public void addExecActionListener(AfterExecActionListener listener) {
        addListener(AfterExecActionListener.class, listener, AfterExecActionListener.METHOD);
    }
    
    @Override
    public void removeExecActionListener(AfterExecActionListener listener) {
        removeListener(AfterExecActionListener.class, listener);
    }
    
    @Override
    public void addExecActionExceptionListener(ExecActionExceptionListener listener) {
        addListener(ExecActionExceptionEvent.class, listener, ExecActionExceptionListener.METHOD);
    }
    
    @Override
    public void removeExecActionExceptionListener(ExecActionExceptionListener listener) {
        removeListener(ExecActionExceptionEvent.class, listener);
    }
    
    @Override
    public void setDummyExecution(boolean dummyExecution) {
        this.dummyExecution = dummyExecution;
    }
    
    static class ExecActionExceptionEventImpl, S, E, C> 
        extends AbstractExecActionEvent implements ExecActionExceptionEvent {
        
        private final TransitionException e;

        ExecActionExceptionEventImpl(TransitionException e, int pos, int size, ActionContext actionContext) {
            super(pos, size, actionContext);
            this.e = e;
        }

        @Override
        public TransitionException getException() {
            return e;
        }
        
    }
    
    static class BeforeExecActionEventImpl, S, E, C> 
            extends AbstractExecActionEvent implements BeforeExecActionEvent {
        
        BeforeExecActionEventImpl(int pos, int size, ActionContext actionContext) {
            super(pos, size, actionContext);
        }

        static , S, E, C> BeforeExecActionEvent get(
                int pos, int size, ActionContext actionContext) {
            return new BeforeExecActionEventImpl(pos, size, actionContext);
        }
    }
    
    static class AfterExecActionEventImpl, S, E, C>
            extends AbstractExecActionEvent implements AfterExecActionEvent {

        AfterExecActionEventImpl(int pos, int size, ActionContext actionContext) {
            super(pos, size, actionContext);
        }

        static , S, E, C> AfterExecActionEvent get(
                int pos, int size, ActionContext actionContext) {
            return new AfterExecActionEventImpl(pos, size, actionContext);
        }
    }
    
    static abstract class AbstractExecActionEvent, S, E, C> 
            implements ActionEvent {
        private ActionContext executionContext;
        private int pos;
        private int size;
        
        AbstractExecActionEvent(int pos, int size, ActionContext actionContext) {
            this.pos = pos;
            this.size = size;
            this.executionContext = actionContext;
        }
        
        @Override
        public Action getExecutionTarget() {
            // user can only read action info but cannot invoke action in the listener method
            return new UncallableActionImpl(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, S, E, C> {
        final Action action;
        final S from;
        final S to;
        final E event;
        final C context;
        final T fsm;
        final int position;
        
        private ActionContext(Action 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 , S, E, C> ActionContext get(
                Action action, S from, S to, E event, C context, T stateMachine, int position) {
            return new ActionContext(action, from, to, event, context, stateMachine, position);
        }

        void run() {
            AbstractStateMachine fsmImpl = (AbstractStateMachine)fsm;
            fsmImpl.beforeActionInvoked(from, to, event, context);
            action.execute(from, to, event, context, fsm);
            fsmImpl.afterActionInvoked(from, to, event, context);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy