io.cloudslang.lang.compiler.modeller.ExecutableBuilder.java Source code

Java tutorial

Introduction

Here is the source code for io.cloudslang.lang.compiler.modeller.ExecutableBuilder.java

Source

/*******************************************************************************
 * (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.compiler.modeller;

import io.cloudslang.lang.compiler.SlangTextualKeys;
import io.cloudslang.lang.compiler.modeller.model.Action;
import io.cloudslang.lang.compiler.modeller.model.Decision;
import io.cloudslang.lang.compiler.modeller.model.Executable;
import io.cloudslang.lang.compiler.modeller.model.ExternalStep;
import io.cloudslang.lang.compiler.modeller.model.Flow;
import io.cloudslang.lang.compiler.modeller.model.Operation;
import io.cloudslang.lang.compiler.modeller.model.Step;
import io.cloudslang.lang.compiler.modeller.model.Workflow;
import io.cloudslang.lang.compiler.modeller.result.ActionModellingResult;
import io.cloudslang.lang.compiler.modeller.result.ExecutableModellingResult;
import io.cloudslang.lang.compiler.modeller.result.StepModellingResult;
import io.cloudslang.lang.compiler.modeller.result.WorkflowModellingResult;
import io.cloudslang.lang.compiler.modeller.transformers.ResultsTransformer;
import io.cloudslang.lang.compiler.modeller.transformers.Transformer;
import io.cloudslang.lang.compiler.parser.model.ParsedSlang;
import io.cloudslang.lang.compiler.validator.ExecutableValidator;
import io.cloudslang.lang.compiler.validator.PreCompileValidator;
import io.cloudslang.lang.entities.ExecutableType;
import io.cloudslang.lang.entities.ScoreLangConstants;
import io.cloudslang.lang.entities.SensitivityLevel;
import io.cloudslang.lang.entities.bindings.Argument;
import io.cloudslang.lang.entities.bindings.Input;
import io.cloudslang.lang.entities.bindings.Output;
import io.cloudslang.lang.entities.bindings.Result;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.iterators.PeekingIterator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static ch.lambdaj.Lambda.filter;
import static ch.lambdaj.Lambda.having;
import static ch.lambdaj.Lambda.on;
import static io.cloudslang.lang.compiler.SlangTextualKeys.DO_EXTERNAL_KEY;
import static io.cloudslang.lang.compiler.SlangTextualKeys.DO_KEY;
import static io.cloudslang.lang.compiler.SlangTextualKeys.FOR_KEY;
import static io.cloudslang.lang.compiler.SlangTextualKeys.NAVIGATION_KEY;
import static io.cloudslang.lang.compiler.SlangTextualKeys.ON_FAILURE_KEY;
import static io.cloudslang.lang.compiler.SlangTextualKeys.PARALLEL_LOOP_KEY;
import static io.cloudslang.lang.compiler.SlangTextualKeys.WORKER_GROUP;
import static io.cloudslang.lang.compiler.SlangTextualKeys.WORKFLOW_KEY;
import static io.cloudslang.lang.entities.ScoreLangConstants.FAILURE_RESULT;
import static io.cloudslang.lang.entities.ScoreLangConstants.LOOP_KEY;
import static io.cloudslang.lang.entities.ScoreLangConstants.NAMESPACE_DELIMITER;
import static io.cloudslang.lang.entities.ScoreLangConstants.SUCCESS_RESULT;
import static io.cloudslang.lang.entities.ScoreLangConstants.WARNING_RESULT;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

public class ExecutableBuilder {

    public static final String UNIQUE_STEP_NAME_MESSAGE_SUFFIX = "Each step name in the workflow must be unique";

    private List<Transformer> transformers;

    private TransformersHandler transformersHandler;

    private DependenciesHelper dependenciesHelper;

    private PreCompileValidator preCompileValidator;

    private ResultsTransformer resultsTransformer;

    private ExecutableValidator executableValidator;

    private List<Transformer> preExecTransformers;
    private List<Transformer> postExecTransformers;

    private List<String> executableAdditionalKeywords = singletonList(SlangTextualKeys.EXECUTABLE_NAME_KEY);
    private List<String> operationAdditionalKeywords = asList(SlangTextualKeys.JAVA_ACTION_KEY,
            SlangTextualKeys.PYTHON_ACTION_KEY, SlangTextualKeys.SEQ_ACTION_KEY);
    private List<String> flowAdditionalKeywords = asList(WORKFLOW_KEY, WORKER_GROUP);
    private List<String> allExecutableAdditionalKeywords;

    private List<Transformer> actionTransformers;
    private List<List<String>> executableConstraintGroups;

    private List<Transformer> preStepTransformers;
    private List<Transformer> postStepTransformers;

    private List<Transformer> externalPreStepTransformers;
    private List<Transformer> externalPostStepTransformers;

    private List<String> stepAdditionalKeyWords = asList(LOOP_KEY, DO_KEY, DO_EXTERNAL_KEY, NAVIGATION_KEY,
            WORKER_GROUP);
    private List<String> parallelLoopValidKeywords = asList(DO_KEY, DO_EXTERNAL_KEY, FOR_KEY, WORKER_GROUP);

    private List<String> seqSupportedResults = asList(SUCCESS_RESULT, WARNING_RESULT, FAILURE_RESULT);

    // @PostConstruct
    public void initScopedTransformersAndKeys() {
        //executable transformers
        preExecTransformers = filterTransformers(Transformer.Scope.BEFORE_EXECUTABLE);
        postExecTransformers = filterTransformers(Transformer.Scope.AFTER_EXECUTABLE);

        //action transformers and keys
        actionTransformers = filterTransformers(Transformer.Scope.ACTION);

        allExecutableAdditionalKeywords = new ArrayList<>(executableAdditionalKeywords.size()
                + operationAdditionalKeywords.size() + flowAdditionalKeywords.size());
        allExecutableAdditionalKeywords.addAll(executableAdditionalKeywords);
        allExecutableAdditionalKeywords.addAll(operationAdditionalKeywords);
        allExecutableAdditionalKeywords.addAll(flowAdditionalKeywords);

        // keys excluding each other
        executableConstraintGroups = new ArrayList<>();
        executableConstraintGroups.add(ListUtils.union(singletonList(WORKFLOW_KEY), operationAdditionalKeywords));

        //step transformers
        preStepTransformers = filterTransformers(Transformer.Scope.BEFORE_STEP);
        postStepTransformers = filterTransformers(Transformer.Scope.AFTER_STEP);

        final List<Transformer> tempPreStepTransformers = filterTransformers(Transformer.Scope.BEFORE_STEP);
        final List<Transformer> tempPostStepTransformers = filterTransformers(Transformer.Scope.AFTER_STEP);

        preStepTransformers = tempPreStepTransformers.stream().filter(t -> t.getType() != Transformer.Type.EXTERNAL)
                .collect(Collectors.toList());

        postStepTransformers = tempPostStepTransformers.stream()
                .filter(t -> t.getType() != Transformer.Type.EXTERNAL).collect(Collectors.toList());

        externalPreStepTransformers = tempPreStepTransformers.stream()
                .filter(t -> t.getType() != Transformer.Type.INTERNAL).collect(Collectors.toList());

        externalPostStepTransformers = tempPostStepTransformers.stream()
                .filter(t -> t.getType() != Transformer.Type.INTERNAL).collect(Collectors.toList());

    }

    private List<Transformer> filterTransformers(Transformer.Scope scope) {
        return filter(having(on(Transformer.class).getScopes().contains(scope)), transformers);
    }

    public ExecutableModellingResult transformToExecutable(ParsedSlang parsedSlang,
            Map<String, Object> executableRawData, SensitivityLevel sensitivityLevel) {
        List<RuntimeException> errors = new ArrayList<>();
        String execName = preCompileValidator.validateExecutableRawData(parsedSlang, executableRawData, errors);
        String workerGroup = (String) executableRawData.get(SlangTextualKeys.WORKER_GROUP);

        errors.addAll(preCompileValidator.checkKeyWords(execName, "", executableRawData,
                ListUtils.union(preExecTransformers, postExecTransformers),
                ParsedSlang.Type.DECISION.equals(parsedSlang.getType()) ? executableAdditionalKeywords
                        : allExecutableAdditionalKeywords,
                executableConstraintGroups));

        Map<String, Serializable> preExecutableActionData = new HashMap<>();
        Map<String, Serializable> postExecutableActionData = new HashMap<>();

        String errorMessagePrefix = "For " + parsedSlang.getType().toString().toLowerCase() + " '" + execName
                + "' syntax is illegal.\n";
        preExecutableActionData.putAll(transformersHandler.runTransformers(executableRawData, preExecTransformers,
                errors, errorMessagePrefix, sensitivityLevel));
        postExecutableActionData.putAll(transformersHandler.runTransformers(executableRawData, postExecTransformers,
                errors, errorMessagePrefix, sensitivityLevel));

        @SuppressWarnings("unchecked")
        List<Input> inputs = (List<Input>) preExecutableActionData.remove(SlangTextualKeys.INPUTS_KEY);
        @SuppressWarnings("unchecked")
        List<Output> outputs = (List<Output>) postExecutableActionData.remove(SlangTextualKeys.OUTPUTS_KEY);

        @SuppressWarnings("unchecked")
        List<Result> results = (List<Result>) postExecutableActionData.remove(SlangTextualKeys.RESULTS_KEY);
        results = results == null ? new ArrayList<Result>() : results;

        String namespace = parsedSlang.getNamespace();
        Set<String> systemPropertyDependencies = null;

        Executable executable;
        boolean isSeqAction = false;

        switch (parsedSlang.getType()) {
        case FLOW:
            resultsTransformer.addDefaultResultsIfNeeded((List) executableRawData.get(SlangTextualKeys.RESULTS_KEY),
                    ExecutableType.FLOW, results, errors);

            Map<String, String> imports = parsedSlang.getImports();

            List<Map<String, Map<String, Object>>> workFlowRawData = preCompileValidator
                    .validateWorkflowRawData(parsedSlang, executableRawData.get(WORKFLOW_KEY), execName, errors);

            Workflow onFailureWorkFlow = getOnFailureWorkflow(workFlowRawData, imports, errors, namespace, execName,
                    sensitivityLevel);

            WorkflowModellingResult workflowModellingResult = compileWorkFlow(workFlowRawData, imports,
                    onFailureWorkFlow, false, namespace, sensitivityLevel);
            errors.addAll(workflowModellingResult.getErrors());
            Workflow workflow = workflowModellingResult.getWorkflow();

            preCompileValidator.validateResultsHaveNoExpression(results, execName, errors);

            Pair<Set<String>, Set<String>> pair = fetchDirectStepsDependencies(workflow);
            Set<String> executableDependencies = pair.getLeft();
            Set<String> externalExecutableDependencies = pair.getRight();
            try {
                systemPropertyDependencies = dependenciesHelper.getSystemPropertiesForFlow(inputs, outputs, results,
                        workflow.getSteps());

            } catch (RuntimeException ex) {
                errors.add(ex);
            }
            executable = new Flow(preExecutableActionData, postExecutableActionData, workflow, namespace, execName,
                    workerGroup, inputs, outputs, results, executableDependencies, externalExecutableDependencies,
                    systemPropertyDependencies);

            break;

        case OPERATION:
            resultsTransformer.addDefaultResultsIfNeeded((List) executableRawData.get(SlangTextualKeys.RESULTS_KEY),
                    ExecutableType.OPERATION, results, errors);

            Map<String, Object> actionRawData = getActionRawData(executableRawData, errors, parsedSlang, execName);
            ActionModellingResult actionModellingResult = compileAction(actionRawData, sensitivityLevel);
            errors.addAll(actionModellingResult.getErrors());
            final Action action = actionModellingResult.getAction();
            executableDependencies = new HashSet<>();

            isSeqAction = actionRawData.containsKey(SlangTextualKeys.SEQ_ACTION_KEY);
            if (!isSeqAction) {
                preCompileValidator.validateResultTypes(results, execName, errors);
                preCompileValidator.validateDefaultResult(results, execName, errors);
            } else {
                preCompileValidator.validateResultsHaveNoExpression(results, execName, errors);
                preCompileValidator.validateResultsWithWhitelist(results, seqSupportedResults, execName, errors);
            }

            try {
                systemPropertyDependencies = dependenciesHelper.getSystemPropertiesForOperation(inputs, outputs,
                        results);

            } catch (RuntimeException ex) {
                errors.add(ex);
            }
            executable = new Operation(preExecutableActionData, postExecutableActionData, action, namespace,
                    execName, inputs, outputs, results, executableDependencies, systemPropertyDependencies);

            break;

        case DECISION:
            resultsTransformer.addDefaultResultsIfNeeded((List) executableRawData.get(SlangTextualKeys.RESULTS_KEY),
                    ExecutableType.DECISION, results, errors);

            preCompileValidator.validateResultTypes(results, execName, errors);
            preCompileValidator.validateDecisionResultsSection(executableRawData, execName, errors);
            preCompileValidator.validateDefaultResult(results, execName, errors);

            try {
                systemPropertyDependencies = dependenciesHelper.getSystemPropertiesForDecision(inputs, outputs,
                        results);
            } catch (RuntimeException ex) {
                errors.add(ex);
            }
            executable = new Decision(preExecutableActionData, postExecutableActionData, namespace, execName,
                    inputs, outputs, results, Collections.<String>emptySet(), systemPropertyDependencies);

            break;

        default:
            throw new RuntimeException("Error compiling " + parsedSlang.getName()
                    + ". It is not of flow, operations or decision type");
        }

        if (!isSeqAction && outputs != null) {
            errors.addAll(validateOutputs(outputs));
        }

        return preCompileValidator.validateResult(parsedSlang, execName,
                new ExecutableModellingResult(executable, errors));
    }

    private List<RuntimeException> validateOutputs(List<Output> outputs) {

        Function<Output, RuntimeException> map = output -> new RuntimeException(
                "'" + SlangTextualKeys.SEQ_OUTPUT_ROBOT_KEY + "' property allowed only for outputs of "
                        + SlangTextualKeys.SEQ_ACTION_KEY + ". Encountered at output " + output.getName());

        return outputs.stream().filter(output -> output.hasRobotProperty()).map(map).collect(Collectors.toList());
    }

    private Map<String, Object> getActionRawData(Map<String, Object> executableRawData,
            List<RuntimeException> errors, ParsedSlang parsedSlang, String execName) {
        Map<String, Object> actionRawData = new HashMap<>();
        Object javaActionRawData = executableRawData.get(SlangTextualKeys.JAVA_ACTION_KEY);
        Object pythonActionRawData = executableRawData.get(SlangTextualKeys.PYTHON_ACTION_KEY);
        Object seqActionRawData = executableRawData.get(SlangTextualKeys.SEQ_ACTION_KEY);
        if (javaActionRawData != null) {
            actionRawData.put(SlangTextualKeys.JAVA_ACTION_KEY,
                    executableRawData.get(SlangTextualKeys.JAVA_ACTION_KEY));
        }
        if (pythonActionRawData != null) {
            actionRawData.put(SlangTextualKeys.PYTHON_ACTION_KEY,
                    executableRawData.get(SlangTextualKeys.PYTHON_ACTION_KEY));
        }
        if (seqActionRawData != null) {
            actionRawData.put(SlangTextualKeys.SEQ_ACTION_KEY,
                    executableRawData.get(SlangTextualKeys.SEQ_ACTION_KEY));
        }
        if (MapUtils.isEmpty(actionRawData)) {
            errors.add(new RuntimeException("Error compiling " + parsedSlang.getName() + ". Operation: " + execName
                    + " has no action data"));
        }
        return actionRawData;
    }

    private Workflow getOnFailureWorkflow(List<Map<String, Map<String, Object>>> workFlowRawData,
            Map<String, String> imports, List<RuntimeException> errors, String namespace, String execName,
            SensitivityLevel sensitivityLevel) {

        Map<String, Map<String, Object>> onFailureStepData = preCompileValidator
                .validateOnFailurePosition(workFlowRawData, execName, errors);

        Workflow onFailureWorkFlow = null;
        if (MapUtils.isNotEmpty(onFailureStepData)) {
            List<Map<String, Map<String, Object>>> onFailureData;
            try {
                //noinspection unchecked
                onFailureData = (List<Map<String, Map<String, Object>>>) onFailureStepData.values().iterator()
                        .next();
            } catch (ClassCastException ex) {
                onFailureData = new ArrayList<>();
                errors.add(new RuntimeException(
                        "Flow: '" + execName + "' syntax is illegal.\nBelow 'on_failure' property there "
                                + "should be a list of steps and not a map"));
            }
            if (CollectionUtils.isNotEmpty(onFailureData)) {
                if (onFailureData.size() > 1) {
                    errors.add(new RuntimeException(
                            "Flow: '" + execName + "' syntax is illegal.\nBelow 'on_failure' property "
                                    + "there should be only one step"));
                }
                handleOnFailureStepNavigationSection(onFailureData, execName, errors);

                WorkflowModellingResult workflowModellingResult = compileWorkFlow(onFailureData, imports, null,
                        true, namespace, sensitivityLevel);
                errors.addAll(workflowModellingResult.getErrors());
                onFailureWorkFlow = workflowModellingResult.getWorkflow();
            } else if (onFailureData == null) {
                errors.add(new RuntimeException("Flow: '" + execName
                        + "' syntax is illegal.\nThere is no step below the 'on_failure' property."));
            }
        }
        return onFailureWorkFlow;
    }

    private void handleOnFailureStepNavigationSection(List<Map<String, Map<String, Object>>> onFailureData,
            String execName, List<RuntimeException> errors) {
        Map.Entry<String, Map<String, Object>> onFailureStep = getFirstOnFailureStep(onFailureData);
        if (onFailureStep.getValue().containsKey(NAVIGATION_KEY)) {
            errors.add(new RuntimeException(
                    "Flow: '" + execName + "' syntax is illegal.\nThe step below 'on_failure' property should "
                            + "not contain a \"navigate\" section."));
        }
    }

    private Map.Entry<String, Map<String, Object>> getFirstOnFailureStep(
            List<Map<String, Map<String, Object>>> onFailureData) {
        Map<String, Map<String, Object>> onFailureStepMap = onFailureData.iterator().next();
        return onFailureStepMap.entrySet().iterator().next();
    }

    private ActionModellingResult compileAction(Map<String, Object> actionRawData,
            SensitivityLevel sensitivityLevel) {
        Map<String, Serializable> actionData = new HashMap<>();

        List<RuntimeException> errors = preCompileValidator.checkKeyWords("action data", "", actionRawData,
                actionTransformers, null, null);

        String errorMessagePrefix = "Action syntax is illegal.\n";
        actionData.putAll(transformersHandler.runTransformers(actionRawData, actionTransformers, errors,
                errorMessagePrefix, sensitivityLevel));

        Action action = new Action(actionData);
        return new ActionModellingResult(action, errors);
    }

    private WorkflowModellingResult compileWorkFlow(List<Map<String, Map<String, Object>>> workFlowRawData,
            Map<String, String> imports, Workflow onFailureWorkFlow, boolean onFailureSection, String namespace,
            SensitivityLevel sensitivityLevel) {

        List<RuntimeException> errors = new ArrayList<>();

        Deque<Step> steps = new LinkedList<>();
        Set<String> stepNames = new HashSet<>();
        Deque<Step> onFailureSteps = !(onFailureSection || onFailureWorkFlow == null) ? onFailureWorkFlow.getSteps()
                : new LinkedList<Step>();
        List<String> onFailureStepNames = getStepNames(onFailureSteps);
        boolean onFailureStepFound = onFailureStepNames.size() > 0;
        String defaultFailure = onFailureStepFound ? onFailureStepNames.get(0) : ScoreLangConstants.FAILURE_RESULT;

        PeekingIterator<Map<String, Map<String, Object>>> iterator = new PeekingIterator<>(
                workFlowRawData.iterator());
        while (iterator.hasNext()) {
            Map<String, Map<String, Object>> stepRawData = iterator.next();
            String stepName = getStepName(stepRawData);
            validateStepName(stepName, errors);
            if (stepNames.contains(stepName) || onFailureStepNames.contains(stepName)) {
                errors.add(new RuntimeException("Step name: \'" + stepName
                        + "\' appears more than once in the workflow. " + UNIQUE_STEP_NAME_MESSAGE_SUFFIX));
            }
            stepNames.add(stepName);
            Map<String, Object> stepRawDataValue;
            String message = "Step: " + stepName + " syntax is illegal.\nBelow step name, there should "
                    + "be a map of values in the format:\ndo:\n\top_name:";
            try {
                stepRawDataValue = stepRawData.values().iterator().next();
                if (MapUtils.isNotEmpty(stepRawDataValue)) {
                    boolean loopKeyFound = stepRawDataValue.containsKey(LOOP_KEY);
                    boolean parallelLoopKeyFound = stepRawDataValue.containsKey(PARALLEL_LOOP_KEY);
                    if (loopKeyFound) {
                        if (parallelLoopKeyFound) {
                            errors.add(new RuntimeException(
                                    "Step: " + stepName + " syntax is illegal.\nBelow step name, "
                                            + "there can be either \'loop\' or \'aync_loop\' key."));
                        }
                        message = "Step: " + stepName + " syntax is illegal.\nBelow the 'loop' keyword, there "
                                + "should be a map of values in the format:\nfor:\ndo:\n\top_name:";
                        @SuppressWarnings("unchecked")
                        Map<String, Object> loopRawData = (Map<String, Object>) stepRawDataValue.remove(LOOP_KEY);
                        stepRawDataValue.putAll(loopRawData);
                    }
                    if (parallelLoopKeyFound) {
                        message = "Step: " + stepName
                                + " syntax is illegal.\nBelow the 'parallel_loop' keyword, there "
                                + "should be a map of values in the format:\nfor:\ndo:\n\top_name:";
                        @SuppressWarnings("unchecked")
                        Map<String, Object> parallelLoopRawData = (Map<String, Object>) stepRawDataValue
                                .remove(PARALLEL_LOOP_KEY);

                        errors.addAll(preCompileValidator.checkKeyWords(stepName,
                                SlangTextualKeys.PARALLEL_LOOP_KEY, parallelLoopRawData, Collections.emptyList(),
                                parallelLoopValidKeywords, null));

                        parallelLoopRawData.put(PARALLEL_LOOP_KEY, parallelLoopRawData.remove(FOR_KEY));
                        stepRawDataValue.putAll(parallelLoopRawData);
                    }
                }
            } catch (ClassCastException ex) {
                stepRawDataValue = new HashMap<>();
                errors.add(new RuntimeException(message));
            }

            String defaultSuccess;
            Map<String, Map<String, Object>> nextStepData = iterator.peek();
            if (nextStepData != null) {
                defaultSuccess = nextStepData.keySet().iterator().next();
            } else {
                defaultSuccess = onFailureSection ? ScoreLangConstants.FAILURE_RESULT : SUCCESS_RESULT;
            }

            String onFailureStepName = onFailureStepFound ? onFailureStepNames.get(0) : null;
            StepModellingResult stepModellingResult = compileStep(stepName, stepRawDataValue, defaultSuccess,
                    imports, defaultFailure, namespace, onFailureStepName, onFailureSection, sensitivityLevel);

            errors.addAll(stepModellingResult.getErrors());
            steps.add(stepModellingResult.getStep());
        }

        if (onFailureStepFound) {
            steps.addAll(onFailureSteps);
        }

        return new WorkflowModellingResult(new Workflow(steps), errors);
    }

    private String getStepName(Map<String, Map<String, Object>> stepRawData) {
        return stepRawData.keySet().iterator().next();
    }

    private String validateStepName(String stepName, List<RuntimeException> errors) {
        try {
            executableValidator.validateStepName(stepName);
        } catch (RuntimeException rex) {
            errors.add(rex);
        }
        return stepName;
    }

    private StepModellingResult compileStep(String stepName, Map<String, Object> stepRawData, String defaultSuccess,
            Map<String, String> imports, String defaultFailure, String namespace, String onFailureStepName,
            boolean onFailureSection, SensitivityLevel sensitivityLevel) {

        List<RuntimeException> errors = new ArrayList<>();
        if (MapUtils.isEmpty(stepRawData)) {
            stepRawData = new HashMap<>();
            errors.add(new RuntimeException("Step: " + stepName + " has no data"));
        }

        final boolean isExternal = stepRawData.containsKey(DO_EXTERNAL_KEY);
        final List<Transformer> localPreStepTransformers = isExternal ? externalPreStepTransformers
                : preStepTransformers;
        final List<Transformer> localPostStepTransformers = isExternal ? externalPostStepTransformers
                : postStepTransformers;

        Map<String, Serializable> preStepData = new HashMap<>();
        Map<String, Serializable> postStepData = new HashMap<>();

        errors.addAll(preCompileValidator.checkKeyWords(stepName, "", stepRawData,
                ListUtils.union(localPreStepTransformers, localPostStepTransformers), stepAdditionalKeyWords,
                null));

        String errorMessagePrefix = "For step '" + stepName + "' syntax is illegal.\n";
        preStepData.putAll(transformersHandler.runTransformers(stepRawData, localPreStepTransformers, errors,
                errorMessagePrefix, sensitivityLevel));
        postStepData.putAll(transformersHandler.runTransformers(stepRawData, localPostStepTransformers, errors,
                errorMessagePrefix, sensitivityLevel));

        replaceOnFailureReference(postStepData, onFailureStepName);

        String workerGroup = (String) stepRawData.get(SlangTextualKeys.WORKER_GROUP);

        String refId = "";
        final List<Argument> arguments = getArgumentsFromDoStep(preStepData);
        final Map<String, Object> doRawData = getRawDataFromDoStep(stepRawData);

        if (MapUtils.isNotEmpty(doRawData)) {
            try {
                String refString = doRawData.keySet().iterator().next();
                refId = resolveReferenceId(refString, imports, namespace, preStepData);
            } catch (RuntimeException rex) {
                errors.add(rex);
            }
        }

        List<Map<String, String>> navigationStrings = getNavigationStrings(postStepData, defaultSuccess,
                defaultFailure, errors);

        Step step = createStep(stepName, onFailureSection, preStepData, postStepData, arguments, workerGroup, refId,
                navigationStrings);
        return new StepModellingResult(step, errors);
    }

    private Step createStep(String stepName, boolean onFailureSection, Map<String, Serializable> preStepData,
            Map<String, Serializable> postStepData, List<Argument> arguments, String workerGroup, String refId,
            List<Map<String, String>> navigationStrings) {
        if (preStepData.containsKey(DO_EXTERNAL_KEY)) {
            return new ExternalStep(stepName, preStepData, postStepData, arguments, navigationStrings, refId,
                    workerGroup, preStepData.containsKey(SlangTextualKeys.PARALLEL_LOOP_KEY), onFailureSection);
        } else {
            return new Step(stepName, preStepData, postStepData, arguments, navigationStrings, refId, workerGroup,
                    preStepData.containsKey(SlangTextualKeys.PARALLEL_LOOP_KEY), onFailureSection);
        }
    }

    @SuppressWarnings("unchecked")
    private List<Argument> getArgumentsFromDoStep(Map<String, Serializable> preStepData) {
        return (List<Argument>) preStepData.getOrDefault(DO_EXTERNAL_KEY, preStepData.get(DO_KEY));
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> getRawDataFromDoStep(Map<String, Object> stepRawData) {
        try {
            return (Map<String, Object>) stepRawData.getOrDefault(DO_EXTERNAL_KEY, stepRawData.get(DO_KEY));
        } catch (ClassCastException ex) {
            return Collections.emptyMap();
        }
    }

    private void replaceOnFailureReference(Map<String, Serializable> postStepData, String onFailureStepName) {
        Serializable navigationData = postStepData.get(NAVIGATION_KEY);
        if (navigationData != null) {
            @SuppressWarnings("unchecked") // from NavigateTransformer
            List<Map<String, String>> navigationStrings = (List<Map<String, String>>) navigationData;
            List<Map<String, String>> transformedNavigationStrings = new ArrayList<>();

            for (Map<String, String> navigation : navigationStrings) {
                Map.Entry<String, String> navigationEntry = navigation.entrySet().iterator().next();
                Map<String, String> transformedNavigation = new HashMap<>(navigation);
                if (navigationEntry.getValue().equals(ON_FAILURE_KEY)) {
                    if (StringUtils.isEmpty(onFailureStepName)) {
                        transformedNavigation.put(navigationEntry.getKey(), ScoreLangConstants.FAILURE_RESULT);
                    } else {
                        transformedNavigation.put(navigationEntry.getKey(), onFailureStepName);
                    }
                } else {
                    transformedNavigation.put(navigationEntry.getKey(), navigationEntry.getValue());
                }
                transformedNavigationStrings.add(transformedNavigation);
            }
            postStepData.put(NAVIGATION_KEY, (Serializable) transformedNavigationStrings);
        }
    }

    private List<Map<String, String>> getNavigationStrings(Map<String, Serializable> postStepData,
            String defaultSuccess, String defaultFailure, List<RuntimeException> errors) {
        @SuppressWarnings("unchecked")
        List<Map<String, String>> navigationStrings = (List<Map<String, String>>) postStepData.get(NAVIGATION_KEY);

        //default navigation
        if (CollectionUtils.isEmpty(navigationStrings)) {
            navigationStrings = new ArrayList<>();
            Map<String, String> successMap = new HashMap<>();
            successMap.put(SUCCESS_RESULT, defaultSuccess);
            Map<String, String> failureMap = new HashMap<>();
            failureMap.put(ScoreLangConstants.FAILURE_RESULT, defaultFailure);
            navigationStrings.add(successMap);
            navigationStrings.add(failureMap);
            return navigationStrings;
        } else {
            return navigationStrings;
        }
    }

    private String resolveReferenceId(String rawReferenceId, Map<String, String> imports, String namespace,
            Map<String, Serializable> preStepData) {
        if (preStepData.containsKey(DO_EXTERNAL_KEY)) {
            return rawReferenceId;
        }
        return resolveDoReferenceId(rawReferenceId, imports, namespace);
    }

    private String resolveDoReferenceId(String rawReferenceId, Map<String, String> imports, String namespace) {

        int numberOfDelimiters = StringUtils.countMatches(rawReferenceId, NAMESPACE_DELIMITER);
        String resolvedReferenceId;

        if (numberOfDelimiters == 0) {
            // implicit namespace
            resolvedReferenceId = namespace + NAMESPACE_DELIMITER + rawReferenceId;
        } else {
            String prefix = StringUtils.substringBefore(rawReferenceId, NAMESPACE_DELIMITER);
            String suffix = StringUtils.substringAfter(rawReferenceId, NAMESPACE_DELIMITER);
            if (MapUtils.isNotEmpty(imports) && imports.containsKey(prefix)) {
                // expand alias
                resolvedReferenceId = imports.get(prefix) + NAMESPACE_DELIMITER + suffix;
            } else {
                // full path without alias expanding
                resolvedReferenceId = rawReferenceId;
            }
        }

        return resolvedReferenceId;
    }

    /**
     * Fetch the first level of the dependencies of the executable (non recursively)
     *
     * @param workflow the workflow of the flow
     * @return a Pair with two sets of dependencies. One set is for CloudSlang dependencies
     *         and the other one is for external dependencies.
     */
    private Pair<Set<String>, Set<String>> fetchDirectStepsDependencies(Workflow workflow) {
        Set<String> dependencies = new HashSet<>();
        Set<String> externalDependencies = new HashSet<>();
        Deque<Step> steps = workflow.getSteps();
        for (Step step : steps) {
            if (step instanceof ExternalStep) {
                externalDependencies.add(step.getRefId());
            } else {
                dependencies.add(step.getRefId());
            }
        }
        return Pair.of(dependencies, externalDependencies);
    }

    private List<String> getStepNames(Deque<Step> steps) {
        List<String> stepNames = new ArrayList<>();
        for (Step step : steps) {
            stepNames.add(step.getName());
        }
        return stepNames;
    }

    public void setTransformers(List<Transformer> transformers) {
        this.transformers = transformers;
    }

    public void setTransformersHandler(TransformersHandler transformersHandler) {
        this.transformersHandler = transformersHandler;
    }

    public void setDependenciesHelper(DependenciesHelper dependenciesHelper) {
        this.dependenciesHelper = dependenciesHelper;
    }

    public void setPreCompileValidator(PreCompileValidator preCompileValidator) {
        this.preCompileValidator = preCompileValidator;
    }

    public void setResultsTransformer(ResultsTransformer resultsTransformer) {
        this.resultsTransformer = resultsTransformer;
    }

    public void setExecutableValidator(ExecutableValidator executableValidator) {
        this.executableValidator = executableValidator;
    }

    public void setPreExecTransformers(List<Transformer> preExecTransformers) {
        this.preExecTransformers = preExecTransformers;
    }

    public void setPostExecTransformers(List<Transformer> postExecTransformers) {
        this.postExecTransformers = postExecTransformers;
    }

    public void setExecutableAdditionalKeywords(List<String> executableAdditionalKeywords) {
        this.executableAdditionalKeywords = executableAdditionalKeywords;
    }

    public void setOperationAdditionalKeywords(List<String> operationAdditionalKeywords) {
        this.operationAdditionalKeywords = operationAdditionalKeywords;
    }

    public void setFlowAdditionalKeywords(List<String> flowAdditionalKeywords) {
        this.flowAdditionalKeywords = flowAdditionalKeywords;
    }

    public void setAllExecutableAdditionalKeywords(List<String> allExecutableAdditionalKeywords) {
        this.allExecutableAdditionalKeywords = allExecutableAdditionalKeywords;
    }

    public void setActionTransformers(List<Transformer> actionTransformers) {
        this.actionTransformers = actionTransformers;
    }

    public void setExecutableConstraintGroups(List<List<String>> executableConstraintGroups) {
        this.executableConstraintGroups = executableConstraintGroups;
    }

    public void setPreStepTransformers(List<Transformer> preStepTransformers) {
        this.preStepTransformers = preStepTransformers;
    }

    public void setPostStepTransformers(List<Transformer> postStepTransformers) {
        this.postStepTransformers = postStepTransformers;
    }

    public void setStepAdditionalKeyWords(List<String> stepAdditionalKeyWords) {
        this.stepAdditionalKeyWords = stepAdditionalKeyWords;
    }

    public void setParallelLoopValidKeywords(List<String> parallelLoopValidKeywords) {
        this.parallelLoopValidKeywords = parallelLoopValidKeywords;
    }
}