strolch/service/src/main/java/li/strolch/execution/command/PlanAndExecuteActivityComma...

222 lines
6.8 KiB
Java

package li.strolch.execution.command;
import static li.strolch.model.State.EXECUTION;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.PARAM_ATOMIC_PARALLEL_EXECUTION;
import java.util.Iterator;
import java.util.Map.Entry;
import li.strolch.execution.Controller;
import li.strolch.execution.policy.ConfirmationPolicy;
import li.strolch.execution.policy.ExecutionPolicy;
import li.strolch.model.State;
import li.strolch.model.activity.Action;
import li.strolch.model.activity.Activity;
import li.strolch.model.activity.IActivityElement;
import li.strolch.model.activity.TimeOrderingVisitor;
import li.strolch.model.parameter.BooleanParameter;
import li.strolch.model.visitor.IActivityElementVisitor;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.utils.dbc.DBC;
public class PlanAndExecuteActivityCommand extends BasePlanningAndExecutionCommand
implements TimeOrderingVisitor, IActivityElementVisitor<Void> {
private Controller controller;
private boolean needsRetriggerOfExecution;
public PlanAndExecuteActivityCommand(StrolchTransaction tx) {
super(tx);
}
public void setController(Controller controller) {
this.controller = controller;
}
private ExecutionPolicy getExecutionPolicy(Action action) {
return this.controller.refreshExecutionPolicy(tx(), action);
}
public boolean needsRetriggerOfExecution() {
return this.needsRetriggerOfExecution;
}
@Override
public void validate() {
DBC.PRE.assertNotNull("controller can not be null!", this.controller);
}
@Override
public void doCommand() {
Activity activity = this.controller.getActivity();
State currentState = activity.getState();
activity.accept(this);
updateOrderState(tx(), activity, currentState, activity.getState());
}
@Override
public Void visitActivity(Activity activity) {
activity.getTimeOrdering().accept(this, activity);
return null;
}
@Override
public Void visitAction(Action action) {
planAndExecute(action);
return null;
}
private void planAndExecute(Action action) {
if (planAction(action))
executeAction(action);
}
private void executeAction(Action action) {
State currentState = action.getState();
if (currentState.compareTo(State.EXECUTED) >= 0 || currentState == EXECUTION)
return;
if (currentState == State.CREATED || currentState == State.PLANNING)
throw new IllegalStateException("Action " + action.getLocator() + " is in illegal state " + currentState);
if (!currentState.canSetToExecution()) {
logger.warn("Action " + action.getLocator() + " can not be executed with state " + currentState);
return;
}
tx().lock(action.getResourceLocator());
ConfirmationPolicy confirmationPolicy = getConfirmationPolicy(action);
ExecutionPolicy executionPolicy = getExecutionPolicy(action);
// we catch all exceptions because we can't undo, thus need to set the state to ERROR in this case
// this is only required because we execute actions in same TX as we set to-executed any previous actions
try {
if (executionPolicy.isStopped()) {
this.controller.removeExecutionPolicy(action);
executionPolicy = getExecutionPolicy(action);
}
if (!executionPolicy.isExecutable(action)) {
logger.info("Action " + action.getLocator() + " is not yet executable.");
return;
}
logger.info("Action " + action.getLocator() + " is now being executed...");
executionPolicy.toExecution(action);
confirmationPolicy.doConfirmation(action);
if (action.getState() == State.EXECUTED)
this.needsRetriggerOfExecution = true;
} catch (Exception e) {
logger.error("Failed to set " + action.getLocator() + " to execution due to " + e.getMessage(), e);
try {
executionPolicy.stop();
} catch (Exception ex) {
logger.error("Failed to stop execution policy for " + action.getLocator(), e);
}
action.setState(State.ERROR);
tx().update(action.getRootElement());
confirmationPolicy.toError(action);
}
}
private boolean planActivity(Activity activity) {
PlanActivityCommand planning = new PlanActivityCommand(tx());
planning.setActivity(activity);
planning.validateAndDoCommand();
return activity.getState().compareTo(State.PLANNED) >= 0;
}
private void unplanActivity(Activity activity) {
UnplanActivityCommand planning = new UnplanActivityCommand(tx());
planning.setActivity(activity);
planning.validateAndDoCommand();
}
private boolean planAction(Action action) {
State currentState = action.getState();
if (currentState.compareTo(State.PLANNED) >= 0)
return true;
getPlanningPolicy(action).plan(action);
if (action.getState().compareTo(State.PLANNED) >= 0) {
// planning is complete, so we can now confirm it
getConfirmationPolicy(action).toPlanned(action);
return true;
}
if (currentState != action.getState() && action.isResourceDefined())
getConfirmationPolicy(action).doConfirmation(action);
logger.info("Failed to plan action " + action.getLocator() + ", thus no execution possible");
return false;
}
@Override
public void visitSeries(Activity activity) {
if (activity.inClosedPhase())
return;
Iterator<Entry<String, IActivityElement>> iter = activity.elementIterator();
while (iter.hasNext()) {
IActivityElement element = iter.next().getValue();
State state = element.getState();
if (element.inClosedPhase())
continue;
// in series, we can never have two Actions in execution, so if we found the action in execution, we stop
if (element instanceof Action && state.inExecutionPhase() && state != State.STOPPED)
break;
// in series, we stop when the first action is set to execution
element.accept(this);
break;
}
}
@Override
public void visitParallel(Activity activity) {
if (activity.inClosedPhase())
return;
BooleanParameter atomicExecutionP = activity.findParameter(PARAM_ATOMIC_PARALLEL_EXECUTION, false);
boolean atomicExecution = atomicExecutionP != null && atomicExecutionP.getValue();
if (atomicExecution) {
if (!planActivity(activity)) {
unplanActivity(activity);
return;
}
// stop execution if at least one action is not executable from this entire tree
boolean anyActionNotExecutable = activity.streamActionsDeep().anyMatch(this::isActionExecutable);
if (anyActionNotExecutable)
return;
}
Iterator<Entry<String, IActivityElement>> iter = activity.elementIterator();
while (iter.hasNext()) {
IActivityElement element = iter.next().getValue();
if (element.inClosedPhase())
continue;
// in parallel, we execute all the actions in the activity
element.accept(this);
}
}
private boolean isActionExecutable(Action action) {
if (!action.getState().canSetToExecution())
return false;
boolean executable = getExecutionPolicy(action).isExecutable(action);
if (!executable)
logger.info("Action " + action.getLocator() + " is not yet executable!");
return !executable;
}
}