
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