cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/steps/StepExecutionData.java
/*******************************************************************************
* (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 com.hp.oo.sdk.content.plugin.StepSerializableSessionObject;
import io.cloudslang.lang.entities.LoopStatement;
import io.cloudslang.lang.entities.NavigationOptions;
import io.cloudslang.lang.entities.ResultNavigation;
import io.cloudslang.lang.entities.RobotGroupStatement;
import io.cloudslang.lang.entities.ScoreLangConstants;
import io.cloudslang.lang.entities.WorkerGroupMetadata;
import io.cloudslang.lang.entities.WorkerGroupStatement;
import io.cloudslang.lang.entities.bindings.Argument;
import io.cloudslang.lang.entities.bindings.InOutParam;
import io.cloudslang.lang.entities.bindings.Output;
import io.cloudslang.lang.entities.bindings.ScriptFunction;
import io.cloudslang.lang.entities.bindings.prompt.Prompt;
import io.cloudslang.lang.entities.bindings.values.Value;
import io.cloudslang.lang.entities.bindings.values.ValueFactory;
import io.cloudslang.lang.entities.utils.ExpressionUtils;
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.bindings.scripts.ScriptEvaluator;
import io.cloudslang.lang.runtime.env.Context;
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 org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static io.cloudslang.lang.entities.ScoreLangConstants.STEP_NAVIGATION_OPTIONS_KEY;
import static io.cloudslang.lang.entities.ScoreLangConstants.WORKER_GROUP;
import static io.cloudslang.lang.entities.ScoreLangConstants.WORKER_GROUP_OVERRIDE;
import static io.cloudslang.lang.entities.ScoreLangConstants.WORKER_GROUP_VALUE;
import static io.cloudslang.lang.entities.bindings.values.Value.toStringSafe;
import static io.cloudslang.score.api.execution.ExecutionParametersConsts.EXECUTION_RUNTIME_SERVICES;
import static java.lang.Double.parseDouble;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
/**
* User: stoneo
* Date: 02/11/2014
* Time: 10:23
*/
@Component
public class StepExecutionData extends AbstractExecutionData {
private static final String DEFAULT_GROUP = "RAS_Operator_Path";
private static final String DEFAULT_ROBOT_GROUP = "Default";
private static final Logger logger = LogManager.getLogger(StepExecutionData.class);
@Autowired
private ArgumentsBinding argumentsBinding;
@Autowired
private OutputsBinding outputsBinding;
@Autowired
private LoopsBinding loopsBinding;
@Autowired
private ScriptEvaluator scriptEvaluator;
@Autowired
private CsMagicVariableHelper magicVariableHelper;
@SuppressWarnings("unused")
public void beginStep(@Param(ScoreLangConstants.STEP_INPUTS_KEY) List<Argument> stepInputs,
@Param(ScoreLangConstants.WORKER_GROUP) WorkerGroupStatement workerGroup,
@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,
@Param(STEP_NAVIGATION_OPTIONS_KEY) List<NavigationOptions> stepNavigationOptions) {
try {
runEnv.removeCallArguments();
runEnv.removeReturnValues();
final int flowDepth = runEnv.getParentFlowStack().size();
prepareNodeName(executionRuntimeServices, nodeName, flowDepth);
Context flowContext = runEnv.getStack().popContext();
Map<String, Value> flowVariables = flowContext.getImmutableViewOfVariables();
fireEvent(
executionRuntimeServices,
runEnv,
ScoreLangConstants.EVENT_STEP_START,
"beginStep execution step started",
LanguageEventData.StepType.STEP,
nodeName,
flowVariables
);
//loops
if (handleLoopStatement(loop, runEnv, nodeName, nextStepId, flowContext, loopsBinding)) {
return;
}
sendStartBindingArgumentsEvent(
stepInputs,
runEnv,
executionRuntimeServices,
"Pre argument binding for step",
nodeName,
flowVariables
);
ReadOnlyContextAccessor contextAccessor = new ReadOnlyContextAccessor(
flowVariables,
magicVariableHelper.getGlobalContext(executionRuntimeServices));
Map<String, Value> boundInputs = argumentsBinding
.bindArguments(stepInputs, contextAccessor,
runEnv.getSystemProperties());
sendEndBindingArgumentsEvent(
stepInputs,
boundInputs,
runEnv,
executionRuntimeServices,
"Step inputs resolved",
nodeName,
flowVariables
);
runEnv.setModifiedArguments(stepInputs.stream().filter(Argument::isExpression)
.collect(Collectors.toList()));
updateCallArgumentsAndPushContextToStack(
runEnv,
flowContext,
boundInputs,
createPrompts(stepInputs));
Value workerGroupValue = flowContext.removeLanguageVariable(WORKER_GROUP_VALUE);
Value workerGroupOverride = flowContext.removeLanguageVariable(WORKER_GROUP_OVERRIDE);
// 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,
toStringSafe(workerGroupValue),
BooleanUtils.toBoolean(toStringSafe(workerGroupOverride)));
//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));
Set<String> unauthorizedFlows = executionRuntimeServices.getUnauthorizedFlows();
if (unauthorizedFlows != null && unauthorizedFlows.contains(refId)) {
throw new RuntimeException("Current user is not allowed to execute this step.");
}
putStepNavigationOptions(runEnv, stepNavigationOptions, nodeName);
} 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);
}
}
@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();
removeStepSerializableSessionObjects(runEnv);
ReturnValues executableReturnValues = runEnv.removeReturnValues();
Map<String, Value> argumentsResultContext = removeStepInputsResultContext(flowContext);
Map<String, Value> executableOutputs = executableReturnValues.getOutputs();
Map<String, Value> globalContext = magicVariableHelper.getGlobalContext(executionRuntimeServices);
ReadOnlyContextAccessor outputsBindingAccessor = new ReadOnlyContextAccessor(
argumentsResultContext,
executableOutputs,
globalContext);
fireEvent(executionRuntimeServices, runEnv, ScoreLangConstants.EVENT_OUTPUT_START, "Output binding started",
LanguageEventData.StepType.STEP, nodeName,
outputsBindingAccessor,
Pair.of(ScoreLangConstants.STEP_PUBLISH_KEY, (Serializable) stepPublishValues),
Pair.of(ScoreLangConstants.STEP_NAVIGATION_KEY, (Serializable) stepNavigationValues),
Pair.of("executableReturnValues", executableReturnValues),
Pair.of("parallelLoop", parallelLoop)
);
final Map<String, Value> publishValues = publishValuesMap(runEnv.getSystemProperties(), stepPublishValues,
parallelLoop, executableOutputs, outputsBindingAccessor, outputsBinding);
flowContext.putVariables(publishValues);
//loops
Map<String, Value> langVariables = flowContext.getImmutableViewOfLanguageVariables();
if (handleEndLoopCondition(runEnv, executionRuntimeServices, previousStepId, breakOn, nodeName, flowContext,
executableReturnValues, outputsBindingAccessor, publishValues, langVariables)) {
return;
}
// 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
final ResultNavigation navigation = getResultNavigation(stepNavigationValues, nodeName,
executableReturnValues, executableResult);
nextPosition = navigation.getNextStepId();
presetResult = navigation.getPresetResult();
}
runEnv.putNextStepPosition(nextPosition);
Map<String, Value> flowVariables = flowContext.getImmutableViewOfVariables();
HashMap<String, Value> outputs = new HashMap<>(flowVariables);
final ReturnValues returnValues = new ReturnValues(outputs, executableResult);
List<NavigationOptions> stepNavigationOptions = runEnv
.removeStepNavigationOptions(nodeName + previousStepId);
final Double roiValue = getRoiValue(executableResult, stepNavigationOptions, flowVariables);
runEnv.putReturnValues(getReturnValues(executableResult, presetResult, outputs));
throwEventOutputEnd(
runEnv,
executionRuntimeServices,
nodeName,
publishValues,
nextPosition,
returnValues,
roiValue,
outputsBindingAccessor,
true
);
executionRuntimeServices.addRoiValue(roiValue);
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);
}
}
@SuppressWarnings("unused")
public void setWorkerGroupStep(
@Param(ScoreLangConstants.WORKER_GROUP) WorkerGroupStatement workerGroup,
@Param(ScoreLangConstants.RUN_ENV) RunEnvironment runEnv,
@Param(EXECUTION_RUNTIME_SERVICES) ExecutionRuntimeServices executionRuntimeServices,
@Param(ScoreLangConstants.NODE_NAME_KEY) String nodeName,
@Param(ScoreLangConstants.NEXT_STEP_ID_KEY) Long nextStepId,
@Param(ScoreLangConstants.ROBOT_GROUP) RobotGroupStatement robotGroup) {
try {
Context flowContext = runEnv.getStack().peekContext();
handleWorkerGroup(workerGroup, flowContext, runEnv, executionRuntimeServices);
handleRobotGroup(robotGroup, flowContext, runEnv, executionRuntimeServices);
runEnv.putNextStepPosition(nextStepId);
} catch (RuntimeException e) {
logger.error("There was an error running the setWorkerGroupStep execution step of: \'" + nodeName +
"\'. Error is: " + e.getMessage());
throw new RuntimeException("Error running: " + nodeName + ": " + e.getMessage(), e);
}
}
private void removeStepSerializableSessionObjects(final RunEnvironment runEnv) {
final int flowDepth = runEnv.getParentFlowStack().size();
runEnv.getSerializableDataMap().entrySet().removeIf(
(Map.Entry<String, ?> entry) -> {
try {
final String key = entry.getKey();
final String valueClassName = entry.getValue()
.getClass()
.getName();
final int entryDepth = Integer.parseInt(key.substring(key.lastIndexOf('_') + 1));
return (entryDepth > flowDepth) &&
valueClassName.equals(StepSerializableSessionObject.class.getName());
} catch (Exception ignore) {
return false;
}
}
);
}
private WorkerGroupMetadata handleWorkerGroup(WorkerGroupStatement workerGroup,
Context flowContext,
RunEnvironment runEnv,
ExecutionRuntimeServices execRuntimeServices) {
WorkerGroupMetadata workerGroupVal = runEnv.getParentFlowStack().computeParentWorkerGroup();
if (workerGroupVal.getValue() != null && workerGroupVal.isOverride()) {
//use the parent worker group
execRuntimeServices.setWorkerGroupName(workerGroupVal.getValue());
} else {
if (workerGroup != null) {
//use the step worker group
String resolvedWorkerGroupValue = computeWorkerValue(workerGroup.getFunctionDependencies(),
workerGroup.getSystemPropertyDependencies(), flowContext, runEnv, workerGroup.getExpression());
workerGroupVal = new WorkerGroupMetadata(resolvedWorkerGroupValue, workerGroup.isOverride());
execRuntimeServices.setWorkerGroupName(workerGroupVal.getValue());
flowContext.putLanguageVariable(WORKER_GROUP, ValueFactory.create(workerGroupVal.getValue()));
} else {
/* It can get here in two situations:
* 1. if there is a parent worker group and override = false
* 2. there is no set worker, in which case the default worker is used
*/
String valueOrDefault = workerGroupVal.getValue() != null ? workerGroupVal.getValue() : DEFAULT_GROUP;
execRuntimeServices.setWorkerGroupName(valueOrDefault);
flowContext.putLanguageVariable(WORKER_GROUP, ValueFactory.create(valueOrDefault));
}
}
execRuntimeServices.setShouldCheckGroup();
flowContext.putLanguageVariable(WORKER_GROUP_VALUE, ValueFactory.create(workerGroupVal.getValue()));
flowContext.putLanguageVariable(WORKER_GROUP_OVERRIDE, ValueFactory.create(workerGroupVal.isOverride()));
return workerGroupVal;
}
private void handleRobotGroup(RobotGroupStatement robotGroup,
Context flowContext,
RunEnvironment runEnv,
ExecutionRuntimeServices execRuntimeServices) {
String robotGroupValue = DEFAULT_ROBOT_GROUP;
if (robotGroup != null) {
robotGroupValue = computeWorkerValue(robotGroup.getFunctionDependencies(),
robotGroup.getSystemPropertyDependencies(),
flowContext, runEnv, robotGroup.getExpression());
} else if (isNotEmpty(execRuntimeServices.getRobotGroupName())) {
robotGroupValue = execRuntimeServices.getRobotGroupName();
}
execRuntimeServices.setRobotGroupName(robotGroupValue);
}
private String computeWorkerValue(Set<ScriptFunction> scriptFunctionSet,
Set<String> systemProperties,
Context flowContext,
RunEnvironment runEnv,
String expression) {
Value resolvedValue;
if (scriptFunctionSet == null && systemProperties == null) {
resolvedValue = ValueFactory.create(expression);
} else {
resolvedValue = scriptEvaluator.evalExpr(expression, flowContext.getImmutableViewOfVariables(),
runEnv.getSystemProperties(), scriptFunctionSet);
}
return resolvedValue.toString();
}
private void putStepNavigationOptions(RunEnvironment runEnv, List<NavigationOptions> stepNavigationOptions,
String nodeName) {
if (CollectionUtils.isNotEmpty(stepNavigationOptions)) {
runEnv.putStepNavigationOptions(nodeName + stepNavigationOptions.get(0).getCurrStepId(),
stepNavigationOptions);
}
}
private Double getRoiValue(String stepExecutableResult, List<NavigationOptions> stepNavigationOptions,
Map<String, Value> flowVariables) {
if (isNotEmpty(stepExecutableResult) && stepNavigationOptions != null) {
for (NavigationOptions navigationOptions : stepNavigationOptions) {
if (navigationOptions.getName().equals(stepExecutableResult)) {
Serializable roi = navigationOptions.getOptions().get(LanguageEventData.ROI);
if (roi instanceof String) {
String expr = ExpressionUtils.extractExpression(roi);
if (isNotEmpty(expr) && flowVariables.containsKey(expr)) {
roi = flowVariables.get(expr).get();
}
}
return roi != null ? parseDouble(roi.toString()) : ExecutionParametersConsts.DEFAULT_ROI_VALUE;
}
}
}
return ExecutionParametersConsts.DEFAULT_ROI_VALUE;
}
private Map<String, Prompt> createPrompts(List<Argument> stepInputs) {
return stepInputs
.stream()
.filter(Argument::hasPrompt)
.collect(toMap(InOutParam::getName, Argument::getPrompt, (x, y) -> y, LinkedHashMap::new));
}
}