/*******************************************************************************
* (c) Copyright 2016 Hewlett-Packard Development Company, L.P.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License v2.0 which accompany this distribution.
*
* The Apache License is available at
* http://www.apache.org/licenses/LICENSE-2.0
*
*******************************************************************************/
package io.cloudslang.lang.runtime.steps;
import com.hp.oo.sdk.content.annotations.Param;
import io.cloudslang.lang.entities.ListLoopStatement;
import io.cloudslang.lang.entities.LoopStatement;
import io.cloudslang.lang.entities.MapLoopStatement;
import io.cloudslang.lang.entities.ResultNavigation;
import io.cloudslang.lang.entities.ScoreLangConstants;
import io.cloudslang.lang.entities.bindings.Argument;
import io.cloudslang.lang.entities.bindings.Output;
import io.cloudslang.lang.entities.bindings.values.Value;
import io.cloudslang.lang.entities.bindings.values.ValueFactory;
import io.cloudslang.lang.runtime.bindings.ArgumentsBinding;
import io.cloudslang.lang.runtime.bindings.LoopsBinding;
import io.cloudslang.lang.runtime.bindings.OutputsBinding;
import io.cloudslang.lang.runtime.env.Context;
import io.cloudslang.lang.runtime.env.ForLoopCondition;
import io.cloudslang.lang.runtime.env.LoopCondition;
import io.cloudslang.lang.runtime.env.ReturnValues;
import io.cloudslang.lang.runtime.env.RunEnvironment;
import io.cloudslang.lang.runtime.events.LanguageEventData;
import io.cloudslang.score.api.execution.ExecutionParametersConsts;
import io.cloudslang.score.lang.ExecutionRuntimeServices;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static io.cloudslang.score.api.execution.ExecutionParametersConsts.EXECUTION_RUNTIME_SERVICES;
/**
* User: stoneo
* Date: 02/11/2014
* Time: 10:23
*/
@Component
public class StepExecutionData extends AbstractExecutionData {
@Autowired
private ArgumentsBinding argumentsBinding;
@Autowired
private OutputsBinding outputsBinding;
@Autowired
private LoopsBinding loopsBinding;
private static final Logger logger = Logger.getLogger(StepExecutionData.class);
@SuppressWarnings("unused")
public void beginStep(@Param(ScoreLangConstants.STEP_INPUTS_KEY) List<Argument> stepInputs,
@Param(ScoreLangConstants.LOOP_KEY) LoopStatement loop,
@Param(ScoreLangConstants.RUN_ENV) RunEnvironment runEnv,
@Param(EXECUTION_RUNTIME_SERVICES) ExecutionRuntimeServices executionRuntimeServices,
@Param(ScoreLangConstants.NODE_NAME_KEY) String nodeName,
//CHECKSTYLE:OFF: checkstyle:parametername
@Param(ExecutionParametersConsts.RUNNING_EXECUTION_PLAN_ID) Long RUNNING_EXECUTION_PLAN_ID,
//CHECKSTYLE:ON
@Param(ScoreLangConstants.NEXT_STEP_ID_KEY) Long nextStepId,
@Param(ScoreLangConstants.REF_ID) String refId) {
try {
fireEvent(executionRuntimeServices, runEnv, ScoreLangConstants.EVENT_STEP_START,
"beginStep execution step started", LanguageEventData.StepType.STEP, nodeName);
runEnv.removeCallArguments();
runEnv.removeReturnValues();
Context flowContext = runEnv.getStack().popContext();
//loops
if (loopStatementExist(loop)) {
LoopCondition loopCondition = loopsBinding
.getOrCreateLoopCondition(loop, flowContext, runEnv.getSystemProperties(), nodeName);
if (loopCondition == null || !loopCondition.hasMore()) {
runEnv.putNextStepPosition(nextStepId);
runEnv.getStack().pushContext(flowContext);
return;
}
if (loopCondition instanceof ForLoopCondition) {
ForLoopCondition forLoopCondition = (ForLoopCondition) loopCondition;
if (loop instanceof ListLoopStatement) {
// normal iteration
String varName = ((ListLoopStatement) loop).getVarName();
loopsBinding.incrementListForLoop(varName, flowContext, forLoopCondition);
} else {
// map iteration
MapLoopStatement mapLoopStatement = (MapLoopStatement) loop;
String keyName = mapLoopStatement.getKeyName();
String valueName = mapLoopStatement.getValueName();
loopsBinding.incrementMapForLoop(keyName, valueName, flowContext, forLoopCondition);
}
}
}
Map<String, Value> flowVariables = flowContext.getImmutableViewOfVariables();
sendStartBindingArgumentsEvent(
stepInputs,
runEnv,
executionRuntimeServices,
"Pre argument binding for step",
nodeName
);
Map<String, Value> boundInputs = argumentsBinding
.bindArguments(stepInputs, flowVariables, runEnv.getSystemProperties());
saveStepInputsResultContext(flowContext, boundInputs);
sendEndBindingArgumentsEvent(
stepInputs,
boundInputs,
runEnv,
executionRuntimeServices,
"Step inputs resolved",
nodeName
);
updateCallArgumentsAndPushContextToStack(runEnv, flowContext, boundInputs);
// request the score engine to switch to the execution plan of the given ref
//CHECKSTYLE:OFF
requestSwitchToRefExecutableExecutionPlan(runEnv, executionRuntimeServices,
RUNNING_EXECUTION_PLAN_ID, refId, nextStepId);
//CHECKSTYLE:ON
// set the start step of the given ref as the next step to execute
// (in the new running execution plan that will be set)
runEnv.putNextStepPosition(executionRuntimeServices.getSubFlowBeginStep(refId));
} catch (RuntimeException e) {
logger.error("There was an error running the beginStep execution step of: \'" + nodeName +
"\'. Error is: " + e.getMessage());
throw new RuntimeException("Error running: " + nodeName + ": " + e.getMessage(), e);
}
}
private boolean loopStatementExist(LoopStatement forLoopStatement) {
return forLoopStatement != null;
}
@SuppressWarnings("unused")
public void endStep(@Param(ScoreLangConstants.RUN_ENV) RunEnvironment runEnv,
@Param(ScoreLangConstants.STEP_PUBLISH_KEY) List<Output> stepPublishValues,
@Param(ScoreLangConstants.STEP_NAVIGATION_KEY)
Map<String, ResultNavigation> stepNavigationValues,
@Param(EXECUTION_RUNTIME_SERVICES) ExecutionRuntimeServices executionRuntimeServices,
@Param(ScoreLangConstants.PREVIOUS_STEP_ID_KEY) Long previousStepId,
@Param(ScoreLangConstants.BREAK_LOOP_KEY) List<String> breakOn,
@Param(ScoreLangConstants.NODE_NAME_KEY) String nodeName,
@Param(ScoreLangConstants.PARALLEL_LOOP_KEY) boolean parallelLoop) {
try {
Context flowContext = runEnv.getStack().popContext();
ReturnValues executableReturnValues = runEnv.removeReturnValues();
fireEvent(executionRuntimeServices, runEnv, ScoreLangConstants.EVENT_OUTPUT_START, "Output binding started",
LanguageEventData.StepType.STEP, nodeName,
Pair.of(ScoreLangConstants.STEP_PUBLISH_KEY, (Serializable) stepPublishValues),
Pair.of(ScoreLangConstants.STEP_NAVIGATION_KEY, (Serializable) stepNavigationValues),
Pair.of("executableReturnValues", executableReturnValues),
Pair.of("parallelLoop", parallelLoop)
);
Map<String, Value> argumentsResultContext = removeStepInputsResultContext(flowContext);
Map<String, Value> publishValues;
if (parallelLoop) {
publishValues = new HashMap<>(executableReturnValues.getOutputs());
} else {
publishValues =
outputsBinding.bindOutputs(
argumentsResultContext,
executableReturnValues.getOutputs(),
runEnv.getSystemProperties(),
stepPublishValues
);
}
flowContext.putVariables(publishValues);
//loops
Map<String, Value> langVariables = flowContext.getImmutableViewOfLanguageVariables();
if (langVariables.containsKey(LoopCondition.LOOP_CONDITION_KEY)) {
LoopCondition loopCondition = (LoopCondition) langVariables.get(LoopCondition.LOOP_CONDITION_KEY).get();
if (!shouldBreakLoop(breakOn, executableReturnValues) && loopCondition.hasMore()) {
runEnv.putNextStepPosition(previousStepId);
runEnv.getStack().pushContext(flowContext);
throwEventOutputEnd(runEnv, executionRuntimeServices, nodeName,
publishValues, previousStepId,
new ReturnValues(publishValues, executableReturnValues.getResult()));
runEnv.getExecutionPath().forward();
return;
} else {
flowContext.removeLanguageVariable(LoopCondition.LOOP_CONDITION_KEY);
}
}
// if this is an endStep method from a branch then next execution step position should ne null
// (end the flow) and result should be the one from the executable
// (navigation is handled in join branches step)
Long nextPosition = null;
String executableResult = executableReturnValues.getResult();
String presetResult = executableResult;
if (!parallelLoop) {
// set the position of the next step - for the use of the navigation
// find in the navigation values the correct next step position, according to the operation result,
// and set it
ResultNavigation navigation = stepNavigationValues.get(executableResult);
if (navigation == null) {
// should always have the executable response mapped to a navigation by the step,
// if not, it is an error
throw new RuntimeException("Step: " + nodeName +
" has no matching navigation for the executable result: " +
executableReturnValues.getResult());
}
nextPosition = navigation.getNextStepId();
presetResult = navigation.getPresetResult();
}
runEnv.putNextStepPosition(nextPosition);
Map<String, Value> flowVariables = flowContext.getImmutableViewOfVariables();
HashMap<String, Value> outputs = new HashMap<>(flowVariables);
ReturnValues returnValues = new ReturnValues(outputs,
presetResult != null ? presetResult : executableResult);
runEnv.putReturnValues(returnValues);
throwEventOutputEnd(runEnv, executionRuntimeServices, nodeName, publishValues, nextPosition, returnValues);
runEnv.getStack().pushContext(flowContext);
runEnv.getExecutionPath().forward();
} catch (RuntimeException e) {
logger.error("There was an error running the endStep execution step of: \'" + nodeName +
"\'. Error is: " + e.getMessage());
throw new RuntimeException("Error running: \'" + nodeName + "\': " + e.getMessage(), e);
}
}
private void throwEventOutputEnd(RunEnvironment runEnv,
ExecutionRuntimeServices executionRuntimeServices,
String nodeName,
Map<String, Value> publishValues,
Long nextPosition,
ReturnValues returnValues) {
fireEvent(executionRuntimeServices, runEnv, ScoreLangConstants.EVENT_OUTPUT_END, "Output binding finished",
LanguageEventData.StepType.STEP, nodeName,
Pair.of(LanguageEventData.OUTPUTS, (Serializable) publishValues),
Pair.of(LanguageEventData.RESULT, returnValues.getResult()),
Pair.of(LanguageEventData.NEXT_STEP_POSITION, nextPosition));
}
private boolean shouldBreakLoop(List<String> breakOn, ReturnValues executableReturnValues) {
return breakOn.contains(executableReturnValues.getResult());
}
private void requestSwitchToRefExecutableExecutionPlan(RunEnvironment runEnv,
ExecutionRuntimeServices executionRuntimeServices,
Long runningExecutionPlanId,
String refId,
Long nextStepId) {
pushParentFlowDataOnStack(runEnv, runningExecutionPlanId, nextStepId);
// request the score engine to switch the execution plan to the one with the given refId once it can
Long subFlowRunningExecutionPlanId = executionRuntimeServices.getSubFlowRunningExecutionPlan(refId);
executionRuntimeServices.requestToChangeExecutionPlan(subFlowRunningExecutionPlanId);
}
private void saveStepInputsResultContext(Context context, Map<String, Value> stepInputsResultContext) {
context.putLanguageVariable(ScoreLangConstants.STEP_INPUTS_RESULT_CONTEXT,
ValueFactory.create((Serializable) stepInputsResultContext));
}
private Map<String, Value> removeStepInputsResultContext(Context context) {
Value rawValue = context.removeLanguageVariable(ScoreLangConstants.STEP_INPUTS_RESULT_CONTEXT);
@SuppressWarnings("unchecked")
Map<String, Value> stepInputsResultContext = rawValue == null ? null : (Map<String, Value>) rawValue.get();
return stepInputsResultContext;
}
}