package org.ovirt.engine.ui.uicommonweb.action;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.ui.frontend.Frontend;
import org.ovirt.engine.ui.uicommonweb.models.Model;
import org.ovirt.engine.ui.uicompat.ConstantsManager;
/**
* This class represents an action. The action is executed by invoking <code>runAction()</code>. A content is added to
* the action by implementing <code>onActionExecuted</code>.
*
* <code>nextAction</code> specified by <code>then(UiAction action)</code> will run just after the source action has
* been executed. <code>parallelAction</code> specified by <code>and(UiAction action)</code> will run before the source
* action is executed (is not dependent of the source action execution).
*
* <code>then(..)</code> and <code>and(..)</code> methods allows creating a flow of actions.
*
* For example-
* <code>actionA.then(actionB).and(actionC).and(actionD).then(actionE).and(actionF).onAllExecutionsFinish(actionG)</code>
*
* <code>model.startProgress(null)</code> is called when the first action in the flow is executed.
* <code>model.stopProgress()</code> is called after the last action in the flow is executed.
*
* The errors from all the actions in the flow can be collected and displayed after the last action in the flow is
* executed.
*
* <code>ActionFlowState</code> is shared between all the action in the flow.
*/
public abstract class UiAction {
private UiAction nextAction;
private SimpleAction finalAction;
private Model model;
private UiAction parallelAction;
private ActionFlowState actionFlowState;
private String name;
/**
* @param model
* the model on which to start and stop the progress.
*/
public UiAction(Model model) {
this(model, ""); //$NON-NLS-1$
}
/**
* @param model
* the model on which to start and stop the progress.
* @param name
* action name
*/
public UiAction(Model model, String name) {
this.model = model;
this.name = name;
}
/**
* The execution of the action should be done by calling this method.
*/
public final void runAction() {
if (actionFlowState == null) {
actionFlowState = new ActionFlowState(1, finalAction);
}
if (model.getProgress() == null) {
model.startProgress();
actionFlowState.setStartedProgress(true);
}
if (parallelAction != null) {
parallelAction.runParallelAction(actionFlowState);
}
if (shouldExecute()) {
internalRunAction();
} else {
runNextAction();
}
}
private final void runAction(ActionFlowState actionFlowState) {
this.actionFlowState = actionFlowState;
if (finalAction != null) {
actionFlowState.setFinalAction(finalAction);
}
runAction();
}
/**
* This method is used if the action should be added to an existing actions flow. The meaning of adding to an
* existing flow is that for example <code>model.stopProgress()</code> and the <code>finishAction</code> will be
* executed just after all the actions in the flow (including this one) will be finished.
*
* @param actionFlowState
* the state of the flow this action wants to join.
*/
public void runParallelAction(ActionFlowState actionFlowState) {
actionFlowState.incBranchCounter();
runAction(actionFlowState);
}
abstract void internalRunAction();
/**
* Specifying an action that will run immediately after <code>this</code> action has finished its execution.
*/
public UiAction then(UiAction nextAction) {
this.nextAction = nextAction;
return nextAction;
}
/**
* Specifying an action that will run before <code>this</code> action has finished its execution, and is not
* dependent on <code>this</code> action's execution.
*/
public UiAction and(UiAction parallelAction) {
this.parallelAction = parallelAction;
return parallelAction;
}
/**
* The <code>finalAction</code> will be executed when the flow is completed (and reached the
* current action).
*
* For example-
* 1. action1.then(action2).onAllExecutionsFinish(finalAction)
* if action1 fails, the finalAction won't be executed (since the flow didn't reach the action it was set on).
* if action1 succeeds, the finalAction will be executed when the flow is completed whether action2 succeeds or not.
* 2. (not recommended!) action1.onAllExecutionsFinish(finalAction).then(action2)
* the final action will be executed whether action1 succeeds or fails when the flow is completed.
*
* It is NOT recommended to set the final action in the middle of the flow!
*/
public void onAllExecutionsFinish(SimpleAction finalAction) {
this.finalAction = finalAction;
}
protected boolean shouldExecute() {
return true;
}
protected UiAction getNextAction() {
return nextAction;
}
void runNextAction() {
if (getNextAction() != null) {
getNextAction().runAction(actionFlowState);
} else {
actionFlowState.decBranchCounter();
tryToFinalize(true);
}
}
public Model getModel() {
return model;
}
public String getName() {
return name;
}
void tryToFinalize(boolean handleErrors) {
if (actionFlowState.isAllDone()) {
if (actionFlowState.isStartedProgress()) {
model.stopProgress();
}
if (handleErrors) {
handleErrors();
}
if (actionFlowState.getFinalAction() != null) {
actionFlowState.getFinalAction().execute();
}
}
}
private void handleErrors() {
if (!getActionFlowState().getFailedActionsMap().isEmpty()) {
Frontend.getInstance().runMultipleActionsFailed(getActionFlowState().getFailedActionsMap(),
innerMessage -> ConstantsManager.getInstance()
.getMessages()
.uiCommonRunActionPartitialyFailed(innerMessage));
}
}
protected ActionFlowState getActionFlowState() {
return actionFlowState;
}
protected static class ActionFlowState {
private int branchCounter;
private SimpleAction finalAction;
Map<VdcActionType, List<VdcReturnValueBase>> failedActionsMap = new HashMap<>();
private boolean startedProgress;
public ActionFlowState(int value, SimpleAction finalAction) {
this.branchCounter = value;
this.finalAction = finalAction;
}
protected void incBranchCounter() {
this.branchCounter++;
}
protected void decBranchCounter() {
this.branchCounter--;
}
boolean isAllDone() {
return branchCounter == 0;
}
private void setFinalAction(SimpleAction finalAction) {
if (this.finalAction != null) {
throw new UnsupportedOperationException("A final action was already set on the flow"); //$NON-NLS-1$
}
this.finalAction = finalAction;
}
private SimpleAction getFinalAction() {
return finalAction;
}
protected void addFailure(VdcActionType actionType, VdcReturnValueBase result) {
List<VdcReturnValueBase> actionTypeResults = failedActionsMap.get(actionType);
if (actionTypeResults == null) {
actionTypeResults = new LinkedList<>();
failedActionsMap.put(actionType, actionTypeResults);
}
actionTypeResults.add(result);
}
protected Map<VdcActionType, List<VdcReturnValueBase>> getFailedActionsMap() {
return failedActionsMap;
}
public boolean isStartedProgress() {
return startedProgress;
}
public void setStartedProgress(boolean startedProgress) {
this.startedProgress = startedProgress;
}
}
}