CloudSlang/cloud-slang

View on GitHub
cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/steps/ExecutableExecutionData.java

Summary

Maintainability
F
3 days
Test Coverage
/*******************************************************************************
 * (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.google.common.collect.Sets;
import com.hp.oo.sdk.content.annotations.Param;
import io.cloudslang.lang.entities.ExecutableType;
import io.cloudslang.lang.entities.ScoreLangConstants;
import io.cloudslang.lang.entities.bindings.Input;
import io.cloudslang.lang.entities.bindings.Output;
import io.cloudslang.lang.entities.bindings.Result;
import io.cloudslang.lang.entities.bindings.prompt.Prompt;
import io.cloudslang.lang.entities.bindings.values.Value;
import io.cloudslang.lang.runtime.bindings.ArgumentsBinding;
import io.cloudslang.lang.runtime.bindings.InputsBinding;
import io.cloudslang.lang.runtime.bindings.OutputsBinding;
import io.cloudslang.lang.runtime.bindings.ResultsBinding;
import io.cloudslang.lang.runtime.bindings.strategies.DebuggerBreakpointsHandler;
import io.cloudslang.lang.runtime.bindings.strategies.MissingInputHandler;
import io.cloudslang.lang.runtime.env.Context;
import io.cloudslang.lang.runtime.env.ParentFlowData;
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.precondition.ExecutionPreconditionService;
import io.cloudslang.score.facade.execution.ExecutionStatus;
import io.cloudslang.score.lang.ExecutionRuntimeServices;
import io.cloudslang.score.lang.SystemContext;
import io.cloudslang.worker.management.WorkerConfigurationService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import static io.cloudslang.lang.entities.ScoreLangConstants.DEBUGGER_EXECUTABLE_INPUTS;
import static io.cloudslang.lang.entities.ScoreLangConstants.USE_EMPTY_VALUES_FOR_PROMPTS_KEY;
import static io.cloudslang.lang.entities.ScoreLangConstants.WORKER_GROUP;
import static io.cloudslang.lang.entities.bindings.values.Value.toStringSafe;
import static io.cloudslang.score.api.execution.ExecutionParametersConsts.EXECUTION_RUNTIME_SERVICES;
import static io.cloudslang.score.api.execution.ExecutionParametersConsts.SYSTEM_CONTEXT;
import static java.lang.String.valueOf;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.BooleanUtils.isTrue;
import static org.apache.commons.lang3.StringUtils.isEmpty;

/**
 * User: stoneo
 * Date: 02/11/2014
 * Time: 10:24
 */
@Component
public class ExecutableExecutionData extends AbstractExecutionData {

    private static final Logger logger = LogManager.getLogger(ExecutableExecutionData.class);
    public static final String ACTION_RETURN_VALUES_KEY = "actionReturnValues";
    private static final String PARENT_RUNNING_ID = "PARENT_RUNNING_ID";
    private static final String RUNNING_PLANS_MAP = "RUNNING_PLANS_MAP";

    private final ResultsBinding resultsBinding;
    private final InputsBinding inputsBinding;
    private final OutputsBinding outputsBinding;
    private final ExecutionPreconditionService executionPreconditionService;
    private final MissingInputHandler missingInputHandler;
    private final CsMagicVariableHelper magicVariableHelper;
    private final DebuggerBreakpointsHandler debuggerBreakpointsHandler;
    private final ArgumentsBinding argumentsBinding;

    @Autowired(required = false)
    private WorkerConfigurationService workerConfigurationService;

    public ExecutableExecutionData(ResultsBinding resultsBinding, InputsBinding inputsBinding,
                                   OutputsBinding outputsBinding, ExecutionPreconditionService preconditionService,
                                   MissingInputHandler missingInputHandler,
                                   CsMagicVariableHelper magicVariableHelper,
                                   DebuggerBreakpointsHandler debuggerBreakpointsHandler,
                                   ArgumentsBinding argumentsBinding) {
        this.resultsBinding = resultsBinding;
        this.inputsBinding = inputsBinding;
        this.outputsBinding = outputsBinding;
        this.executionPreconditionService = preconditionService;
        this.missingInputHandler = missingInputHandler;
        this.magicVariableHelper = magicVariableHelper;
        this.debuggerBreakpointsHandler = debuggerBreakpointsHandler;
        this.argumentsBinding = argumentsBinding;
    }

    public void startExecutable(@Param(ScoreLangConstants.EXECUTABLE_INPUTS_KEY) List<Input> executableInputs,
                                @Param(ScoreLangConstants.RUN_ENV) RunEnvironment runEnv,
                                @Param(ScoreLangConstants.USER_INPUTS_KEY) Map<String, ? extends Value> userInputs,
                                @Param(EXECUTION_RUNTIME_SERVICES) ExecutionRuntimeServices executionRuntimeServices,
                                @Param(ScoreLangConstants.NODE_NAME_KEY) String nodeName,
                                @Param(ScoreLangConstants.NEXT_STEP_ID_KEY) Long nextStepId,
                                @Param(ScoreLangConstants.EXECUTABLE_TYPE) ExecutableType executableType,
                                @Param(SYSTEM_CONTEXT) SystemContext systemContext,
                                @Param(USE_EMPTY_VALUES_FOR_PROMPTS_KEY) Boolean useEmptyValuesForPrompts) {
        try {
            if (runEnv.isContextModified()) {
                rebindArguments(runEnv, executionRuntimeServices, nodeName);
            }

            Map<String, Value> callArguments = runEnv.removeCallArguments();
            List<Input> mutableInputList = new ArrayList<>(executableInputs);

            if (userInputs != null) {
                callArguments.putAll(userInputs);
                // merge is done only for flows that have extra inputs besides those defined as "flow inputs"
                if (executionRuntimeServices.getMergeUserInputs()) {
                    Map<String, Input> executableInputsMap = mutableInputList.stream()
                            .collect(toMap(Input::getName, Function.identity()));
                    for (String inputName : userInputs.keySet()) {
                        Value inputValue = userInputs.get(inputName);
                        Input inputToUpdate = executableInputsMap.get(inputName);
                        if (inputToUpdate != null) {
                            // In case values are the same, do not compute the value again to avoid seeing it as new
                            if (inputToUpdate.getValue() != null && inputToUpdate.getValue().equals(inputValue)) {
                                callArguments.remove(inputName);
                            } else {
                                Input updatedInput = new Input.InputBuilder(inputToUpdate, inputValue)
                                        .build();
                                mutableInputList.set(mutableInputList.indexOf(inputToUpdate), updatedInput);
                            }
                        } else {
                            Input toAddInput = new Input.InputBuilder(inputName, inputValue).build();
                            mutableInputList.add(toAddInput);
                        }
                    }
                }
            }

            executableInputs = Collections.unmodifiableList(mutableInputList);

            //restore what was already prompted and add newly prompted values
            Map<String, Value> promptedValues = runEnv.removePromptedValues();
            promptedValues.putAll(missingInputHandler.applyPromptInputValues(systemContext, executableInputs));

            Map<String, Prompt> promptArguments = runEnv.removePromptArguments();

            List<Input> newExecutableInputs =
                    addUserDefinedStepInputs(executableInputs, callArguments, promptArguments);
            if (systemContext.get(DEBUGGER_EXECUTABLE_INPUTS) != null) {
                Map<String, Value> debuggerInputs =
                        (Map<String, Value>) systemContext.remove(DEBUGGER_EXECUTABLE_INPUTS);
                List<Input> newInputs = debuggerInputs.entrySet().stream().map(entry ->
                                new Input.InputBuilder(entry.getKey(), entry.getValue()).withRequired(false).build())
                        .collect(toList());
                List<Input> updatedExecutableInputs = new ArrayList<>(newExecutableInputs.size() +
                        newInputs.size());
                updatedExecutableInputs.addAll(executableInputs);
                updatedExecutableInputs.addAll(newInputs);
                newExecutableInputs = updatedExecutableInputs;
            }
            LanguageEventData.StepType stepType = LanguageEventData.convertExecutableType(executableType);
            sendStartBindingInputsEvent(
                    newExecutableInputs,
                    runEnv,
                    executionRuntimeServices,
                    "Pre Input binding for " + stepType,
                    stepType,
                    nodeName,
                    callArguments
            );
            Map<String, Value> magicVariables = magicVariableHelper.getGlobalContext(executionRuntimeServices);
            List<Input> missingInputs = new ArrayList<>();
            ReadOnlyContextAccessor context = new ReadOnlyContextAccessor(callArguments, magicVariables);
            boolean isCslangPromptEnabled = executionRuntimeServices.getCslangPromptsEnabledFlag();
            Map<String, Value> boundInputValues = inputsBinding.bindInputs(
                    newExecutableInputs,
                    context.getMergedContexts(),
                    promptedValues,
                    runEnv.getSystemProperties(),
                    missingInputs,
                    isTrue(useEmptyValuesForPrompts),
                    promptArguments,
                    isCslangPromptEnabled);

            boolean continueToNext = true;
            if (systemContext.containsKey(ScoreLangConstants.USER_INTERRUPT)) {
                Long parentRid = null;
                if (systemContext.containsKey(PARENT_RUNNING_ID)) {
                    parentRid = (Long) systemContext.get(PARENT_RUNNING_ID);
                }
                continueToNext = !debuggerBreakpointsHandler.handleBreakpoints(
                        systemContext,
                        runEnv,
                        executionRuntimeServices,
                        stepType,
                        nodeName,
                        extractUuid(executionRuntimeServices, parentRid));
            }

            //if there are any missing required input after binding
            // try to resolve it using provided missing input handler
            if (CollectionUtils.isNotEmpty(missingInputs)) {
                boolean canContinue = missingInputHandler.resolveMissingInputs(
                        missingInputs,
                        systemContext,
                        runEnv,
                        executionRuntimeServices,
                        stepType,
                        nodeName,
                        isTrue(useEmptyValuesForPrompts));

                if (!canContinue) {
                    //we must keep the state unchanged}
                    runEnv.putCallArguments(callArguments);
                    runEnv.putPromptArguments(promptArguments);
                    //keep what was already prompted
                    runEnv.keepPromptedValues(promptedValues);
                    return;
                }
            }

            Map<String, Value> actionArguments = new HashMap<>(boundInputValues);

            //updated stored input arguments to be later used for output binding
            saveStepInputsResultContext(runEnv, callArguments, promptedValues);

            //done with the user inputs, don't want it to be available in next startExecutable steps..
            if (userInputs != null) {
                userInputs.clear();
            }

            updateCallArgumentsAndPushContextToStack(runEnv,
                    new Context(boundInputValues, magicVariables), actionArguments, new HashMap<>(), continueToNext);

            sendEndBindingInputsEvent(
                    newExecutableInputs,
                    boundInputValues,
                    runEnv,
                    executionRuntimeServices,
                    "Post Input binding for " + stepType,
                    stepType,
                    nodeName,
                    callArguments);

            executionRuntimeServices.setShouldCheckGroup();
            if (continueToNext) {
                // put the next step position for the navigation
                runEnv.putNextStepPosition(nextStepId);
                runEnv.getExecutionPath().down();
            }
        } catch (RuntimeException e) {
            logger.error("There was an error running the start executable execution step of: \'" + nodeName +
                    "\'.\n\tError is: " + e.getMessage());
            throw new RuntimeException("Error running: \'" + nodeName + "\'.\n\t " + e.getMessage(), e);
        }

    }

    private List<Input> addUserDefinedStepInputs(List<Input> executableInputs,
                                                 Map<String, Value> callArguments,
                                                 Map<String, Prompt> promptArguments) {
        final List<Input> inputs = extractUserDefinedStepInputs(executableInputs, promptArguments, callArguments);

        if (inputs.size() == 0) {
            return executableInputs;
        }

        List<Input> newExecutableInputs = new ArrayList<>(executableInputs.size() + inputs.size());
        newExecutableInputs.addAll(executableInputs);
        newExecutableInputs.addAll(inputs);

        return newExecutableInputs;
    }

    /**
     * This method is executed by the finishExecutable execution step of an operation or flow
     *
     * @param runEnv                   the run environment object
     * @param executableOutputs        the operation outputs data
     * @param executableResults        the operation results data
     * @param executionRuntimeServices services supplied by score engine for handling the execution
     */
    public void finishExecutable(@Param(ScoreLangConstants.RUN_ENV) RunEnvironment runEnv,
                                 @Param(ScoreLangConstants.EXECUTABLE_OUTPUTS_KEY) List<Output> executableOutputs,
                                 @Param(ScoreLangConstants.EXECUTABLE_RESULTS_KEY) List<Result> executableResults,
                                 @Param(EXECUTION_RUNTIME_SERVICES) ExecutionRuntimeServices executionRuntimeServices,
                                 @Param(ScoreLangConstants.NODE_NAME_KEY) String nodeName,
                                 @Param(ScoreLangConstants.EXECUTABLE_TYPE) ExecutableType executableType) {
        try {
            runEnv.getExecutionPath().up();
            Context operationContext = runEnv.getStack().popContext();
            Map<String, Value> operationVariables = operationContext == null ?
                    null : operationContext.getImmutableViewOfVariables();

            Context flowContext = runEnv.getStack().peekContext();
            if (executableType == ExecutableType.FLOW && flowContext != null) {
                String workerGroup = toStringSafe(flowContext.removeLanguageVariable(WORKER_GROUP));
                if (workerGroup != null) {
                    executionRuntimeServices.setWorkerGroupName(workerGroup);
                    executionRuntimeServices.setShouldCheckGroup();
                }
            }

            ReturnValues actionReturnValues = buildReturnValues(runEnv, executableType);
            LanguageEventData.StepType stepType = LanguageEventData.convertExecutableType(executableType);
            fireEvent(
                    executionRuntimeServices,
                    runEnv,
                    ScoreLangConstants.EVENT_OUTPUT_START,
                    "Output binding started",
                    stepType,
                    nodeName,
                    operationVariables,
                    Pair.of(ScoreLangConstants.EXECUTABLE_OUTPUTS_KEY, (Serializable) executableOutputs),
                    Pair.of(ScoreLangConstants.EXECUTABLE_RESULTS_KEY, (Serializable) executableResults),
                    Pair.of(ACTION_RETURN_VALUES_KEY,
                            executableType == ExecutableType.OPERATION ?
                                    new ReturnValues(new HashMap<String, Value>(), actionReturnValues.getResult()) :
                                    actionReturnValues)
            );

            // Resolving the result of the operation/flow
            String result = resultsBinding.resolveResult(
                    operationVariables,
                    actionReturnValues.getOutputs(),
                    runEnv.getSystemProperties(),
                    executableResults,
                    actionReturnValues.getResult()
            );

            ReadOnlyContextAccessor outputsBindingAccessor = new ReadOnlyContextAccessor(operationVariables,
                    actionReturnValues.getOutputs(), magicVariableHelper.getGlobalContext(executionRuntimeServices));
            Map<String, Value> operationReturnOutputs =
                    outputsBinding.bindOutputs(
                            outputsBindingAccessor,
                            runEnv.getSystemProperties(),
                            executableOutputs
                    );

            ReturnValues returnValues = new ReturnValues(operationReturnOutputs, result);
            runEnv.putReturnValues(returnValues);
            fireEvent(
                    executionRuntimeServices,
                    runEnv,
                    ScoreLangConstants.EVENT_OUTPUT_END,
                    "Output binding finished",
                    stepType,
                    nodeName,
                    operationVariables,
                    Pair.of(LanguageEventData.OUTPUTS, (Serializable) operationReturnOutputs),
                    Pair.of(LanguageEventData.RESULT, returnValues.getResult())
            );

            // If we have parent flow data on the stack, we pop it and request the score engine to switch
            // to the parent execution plan id once it can, and we set the next position that was stored there
            // for the use of the navigation
            if (!runEnv.getParentFlowStack().isEmpty()) {
                handleNavigationToParent(runEnv, executionRuntimeServices);
            } else {
                fireEvent(
                        executionRuntimeServices,
                        runEnv,
                        ScoreLangConstants.EVENT_EXECUTION_FINISHED,
                        "Execution finished running",
                        stepType,
                        nodeName,
                        operationVariables,
                        Pair.of(LanguageEventData.RESULT, returnValues.getResult()),
                        Pair.of(LanguageEventData.OUTPUTS, (Serializable) operationReturnOutputs)
                );
            }
        } catch (RuntimeException e) {
            logger.error("There was an error running the finish executable execution step of: \'" + nodeName +
                    "\'.\n\tError is: " + e.getMessage());
            throw new RuntimeException("Error running: \'" + nodeName + "\'.\n\t" + e.getMessage(), e);
        }
    }

    public void canExecute(@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) {
        try {
            // check canceled, if it is then set next position to null and flow termination type
            if (workerConfigurationService != null &&
                    (workerConfigurationService.isExecutionCancelled(executionRuntimeServices.getExecutionId()) ||
                            (executionRuntimeServices.getFlowTerminationType() == ExecutionStatus.CANCELED))) {
                // NOTE: an execution can be cancelled directly from CancelExecutionService, if it's currently paused.
                // Thus, if you change the code here, please check CancelExecutionService as well.
                executionRuntimeServices.setFlowTerminationType(ExecutionStatus.CANCELED);
                runEnv.putNextStepPosition(null);
                return;
            }
            if (!isEmpty(runEnv.getExecutionPath().getParentPath())) {
                // If it is start of a sub flow then the check should not happen
                runEnv.putNextStepPosition(nextStepId);
                return;
            }

            if (!executionPreconditionService.canExecute(valueOf(executionRuntimeServices.getExecutionId()),
                    executionRuntimeServices.isEnterpriseMode())) {
                logger.warn("Execution precondition not fulfilled. Waiting for it to be true.");

                if (!executionRuntimeServices.getPreconditionNotFulfilled()) {
                    executionRuntimeServices.setPreconditionNotFulfilled();
                }
            } else if (executionRuntimeServices.getPreconditionNotFulfilled()) {
                executionRuntimeServices.removePreconditionNotFulfilled();
            }

            runEnv.putNextStepPosition(nextStepId);
        } catch (RuntimeException e) {
            logger.error("There was an error running the finish executable execution step of: \'" + nodeName +
                    "\'.\n\tError is: " + e.getMessage());
            throw new RuntimeException("Error running: \'" + nodeName + "\'.\n\t" + e.getMessage(), e);
        }
    }

    private void handleNavigationToParent(RunEnvironment runEnv, ExecutionRuntimeServices executionRuntimeServices) {
        ParentFlowData parentFlowData = runEnv.getParentFlowStack().popParentFlowData();
        executionRuntimeServices.requestToChangeExecutionPlan(parentFlowData.getRunningExecutionPlanId());
        runEnv.putNextStepPosition(parentFlowData.getPosition());
    }

    private ReturnValues buildReturnValues(RunEnvironment runEnvironment, ExecutableType executableType) {
        ReturnValues returnValues = runEnvironment.removeReturnValues();
        switch (executableType) {
            case DECISION:
                returnValues = new ReturnValues(Collections.<String, Value>emptyMap(), null);
                break;
            case FLOW:
                break;
            case OPERATION:
                break;
            default:
                throw new RuntimeException("Unrecognized type: " + executableType);
        }
        return returnValues;
    }

    private List<Input> extractUserDefinedStepInputs(List<Input> inputs,
                                                     Map<String, Prompt> prompts,
                                                     Map<String, Value> callArguments) {
        Set<String> inputNames = inputs.stream().map(Input::getName).collect(toCollection(LinkedHashSet::new));
        Set<String> promptInputNames = prompts.keySet();

        return Sets
                .difference(promptInputNames, inputNames)
                .stream()
                .map(additionalInputName ->
                        new Input
                                .InputBuilder(additionalInputName, callArguments.get(additionalInputName))
                                .withPrompt(prompts.get(additionalInputName))
                                .build()
                )
                .collect(toList());

    }

    public void saveStepInputsResultContext(RunEnvironment runEnv,
                                            Map<String, Value> originalCallArguments,
                                            Map<String, Value> promptedValues) {
        Context flowContext = runEnv.getStack().peekContext();
        if (flowContext != null) {
            Map<String, Value> finalActionArguments = new HashMap<>(originalCallArguments);
            finalActionArguments.putAll(promptedValues);
            saveStepInputsResultContext(flowContext, finalActionArguments);
        }
    }

    private String extractUuid(ExecutionRuntimeServices executionRuntimeServices, Long parentRid) {
        String parentName;
        String newNodeName;
        if (StringUtils.isEmpty(parentName = executionRuntimeServices.extractParentNameFromRunId(parentRid)) ||
                StringUtils.isEmpty(newNodeName = executionRuntimeServices.getNodeName())) {
            return Strings.EMPTY;
        } else {
            return parentName + "." + newNodeName;
        }

    }

    private void rebindArguments(RunEnvironment runEnvironment,
                                 ExecutionRuntimeServices executionRuntimeServices,
                                 String nodeName) {
        Context flowContext = runEnvironment.getStack().peekContext();
        Map<String, Value> flowVariables = flowContext.getImmutableViewOfVariables();

        ReadOnlyContextAccessor contextAccessor = new ReadOnlyContextAccessor(
                flowVariables,
                magicVariableHelper.getGlobalContext(executionRuntimeServices));

        Map<String, Value> boundInputs = argumentsBinding
                .bindArguments(runEnvironment.getModifiedArguments(), contextAccessor,
                        runEnvironment.getSystemProperties());

        runEnvironment.putCallArguments(boundInputs);

        sendEndBindingArgumentsEvent(
                runEnvironment.getModifiedArguments(),
                boundInputs,
                runEnvironment,
                executionRuntimeServices,
                "Step inputs resolved",
                nodeName,
                flowVariables
        );
        runEnvironment.setContextModified(false);
    }
}