/******************************************************************************* * (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.Output; import io.cloudslang.lang.entities.bindings.values.Value; import io.cloudslang.lang.entities.bindings.values.ValueFactory; import io.cloudslang.lang.runtime.RuntimeConstants; import io.cloudslang.lang.runtime.bindings.OutputsBinding; import io.cloudslang.lang.runtime.bindings.ParallelLoopBinding; 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.EndBranchDataContainer; import io.cloudslang.score.api.execution.ExecutionParametersConsts; import io.cloudslang.score.lang.ExecutionRuntimeServices; import io.cloudslang.score.lang.SystemContext; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang.SerializationUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.apache.log4j.Logger; import org.python.google.common.collect.Lists; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static io.cloudslang.score.api.execution.ExecutionParametersConsts.EXECUTION_RUNTIME_SERVICES; /** * Date: 3/25/2015 * * @author Bonczidai Levente */ @Component public class ParallelLoopExecutionData extends AbstractExecutionData { public static final String BRANCH_EXCEPTION_PREFIX = "Error running branch"; @Autowired private ParallelLoopBinding parallelLoopBinding; @Autowired private OutputsBinding outputsBinding; private static final Logger logger = Logger.getLogger(ParallelLoopExecutionData.class); public void addBranches(@Param(ScoreLangConstants.PARALLEL_LOOP_STATEMENT_KEY) LoopStatement parallelLoopStatement, @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.BRANCH_BEGIN_STEP_ID_KEY) Long branchBeginStep, @Param(ScoreLangConstants.REF_ID) String refId) { try { Context flowContext = runEnv.getStack().popContext(); List<Value> splitData = parallelLoopBinding .bindParallelLoopList(parallelLoopStatement, flowContext, runEnv.getSystemProperties(), nodeName); fireEvent(executionRuntimeServices, ScoreLangConstants.EVENT_SPLIT_BRANCHES, "parallel loop expression bound", runEnv.getExecutionPath().getCurrentPath(), LanguageEventData.StepType.STEP, nodeName, Pair.of(LanguageEventData.BOUND_PARALLEL_LOOP_EXPRESSION, (Serializable) splitData)); runEnv.putNextStepPosition(nextStepId); runEnv.getExecutionPath().down(); for (Value splitItem : splitData) { // first fire event fireEvent(executionRuntimeServices, ScoreLangConstants.EVENT_BRANCH_START, "parallel loop branch created", runEnv.getExecutionPath().getCurrentPath(), LanguageEventData.StepType.STEP, nodeName, Pair.of(ScoreLangConstants.REF_ID, refId), Pair.of(RuntimeConstants.SPLIT_ITEM_KEY, splitItem)); // take path down one level runEnv.getExecutionPath().down(); RunEnvironment branchRuntimeEnvironment = (RunEnvironment) SerializationUtils.clone(runEnv); branchRuntimeEnvironment.resetStacks(); Context branchContext = (Context) SerializationUtils.clone(flowContext); if (parallelLoopStatement instanceof ListLoopStatement) { branchContext.putVariable(((ListLoopStatement) parallelLoopStatement).getVarName(), splitItem); } else if (parallelLoopStatement instanceof MapLoopStatement) { branchContext.putVariable(((MapLoopStatement) parallelLoopStatement).getKeyName(), (Value) ((ImmutablePair) splitItem.get()).getLeft()); branchContext.putVariable(((MapLoopStatement) parallelLoopStatement).getValueName(), (Value) ((ImmutablePair) splitItem.get()).getRight()); } updateCallArgumentsAndPushContextToStack(branchRuntimeEnvironment, branchContext, new HashMap<String, Value>()); createBranch( branchRuntimeEnvironment, executionRuntimeServices, refId, branchBeginStep); // take path up level runEnv.getExecutionPath().up(); // forward for next branch runEnv.getExecutionPath().forward(); } updateCallArgumentsAndPushContextToStack(runEnv, flowContext, new HashMap<String, Value>()); } catch (RuntimeException e) { logger.error("There was an error running the add branches execution step of: \'" + nodeName + "\'. Error is: " + e.getMessage()); throw new RuntimeException("Error running: " + nodeName + ": " + e.getMessage(), e); } } public void joinBranches(@Param(ScoreLangConstants.RUN_ENV) RunEnvironment runEnv, @Param(EXECUTION_RUNTIME_SERVICES) ExecutionRuntimeServices executionRuntimeServices, @Param(ScoreLangConstants.STEP_PUBLISH_KEY) List<Output> stepPublishValues, @Param(ScoreLangConstants.STEP_NAVIGATION_KEY) Map<String, ResultNavigation> stepNavigationValues, @Param(ScoreLangConstants.NODE_NAME_KEY) String nodeName) { try { runEnv.getExecutionPath().up(); List<Map<String, Serializable>> branchesContext = Lists.newArrayList(); Context flowContext = runEnv.getStack().popContext(); collectBranchesData(executionRuntimeServices, nodeName, branchesContext); Map<String, Value> publishValues = bindPublishValues( runEnv, executionRuntimeServices, stepPublishValues, stepNavigationValues, nodeName, branchesContext ); flowContext.putVariables(publishValues); String parallelLoopResult = getParallelLoopResult(branchesContext); handleNavigationAndReturnValues(runEnv, executionRuntimeServices, stepNavigationValues, nodeName, publishValues, parallelLoopResult); runEnv.getStack().pushContext(flowContext); runEnv.getExecutionPath().forward(); } catch (RuntimeException e) { logger.error("There was an error running the joinBranches execution step of: \'" + nodeName + "\'. Error is: " + e.getMessage()); throw new RuntimeException("Error running: \'" + nodeName + "\': \n" + e.getMessage(), e); } } private void handleNavigationAndReturnValues( RunEnvironment runEnv, ExecutionRuntimeServices executionRuntimeServices, Map<String, ResultNavigation> stepNavigationValues, String nodeName, Map<String, Value> publishValues, String parallelLoopResult) { // 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 parallel loop result, // and set it ResultNavigation navigation = stepNavigationValues.get(parallelLoopResult); 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 parallel loop result: " + parallelLoopResult); } Long nextStepPosition = navigation.getNextStepId(); String presetResult = navigation.getPresetResult(); HashMap<String, Value> outputs = new HashMap<>(publishValues); ReturnValues returnValues = new ReturnValues(outputs, presetResult != null ? presetResult : parallelLoopResult); fireEvent(executionRuntimeServices, runEnv, ScoreLangConstants.EVENT_JOIN_BRANCHES_END, "Parallel loop 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, nextStepPosition)); runEnv.putReturnValues(returnValues); runEnv.putNextStepPosition(nextStepPosition); } private String getParallelLoopResult(List<Map<String, Serializable>> branchesContext) { // if one of the branches failed then return with FAILURE, otherwise return with SUCCESS String parallelLoopResult = ScoreLangConstants.SUCCESS_RESULT; for (Map<String, Serializable> branchContext : branchesContext) { String branchResult = (String) branchContext.get(ScoreLangConstants.BRANCH_RESULT_KEY); if (branchResult.equals(ScoreLangConstants.FAILURE_RESULT)) { parallelLoopResult = ScoreLangConstants.FAILURE_RESULT; break; } } return parallelLoopResult; } private Map<String, Value> bindPublishValues( RunEnvironment runEnv, ExecutionRuntimeServices executionRuntimeServices, List<Output> stepPublishValues, Map<String, ResultNavigation> stepNavigationValues, String nodeName, List<Map<String, Serializable>> branchesContext) { Map<String, Value> publishContext = new HashMap<>(); publishContext.put(RuntimeConstants.BRANCHES_CONTEXT_KEY, ValueFactory.create((Serializable) branchesContext)); fireEvent( executionRuntimeServices, runEnv, ScoreLangConstants.EVENT_JOIN_BRANCHES_START, "Parallel loop output binding started", LanguageEventData.StepType.STEP, nodeName, Pair.of(ScoreLangConstants.STEP_PUBLISH_KEY, (Serializable) stepPublishValues), Pair.of(ScoreLangConstants.STEP_NAVIGATION_KEY, (Serializable) stepNavigationValues)); return outputsBinding.bindOutputs( Collections.<String, Value>emptyMap(), publishContext, runEnv.getSystemProperties(), stepPublishValues ); } private void collectBranchesData( ExecutionRuntimeServices executionRuntimeServices, String nodeName, List<Map<String, Serializable>> branchesContext) { List<EndBranchDataContainer> branches = executionRuntimeServices.getFinishedChildBranchesData(); for (EndBranchDataContainer branch : branches) { checkExceptionInBranch(branch); Map<String, Serializable> branchContext = branch.getContexts(); RunEnvironment branchRuntimeEnvironment = (RunEnvironment) branchContext.get(ScoreLangConstants.RUN_ENV); Map<String, Serializable> branchContextMap = convert(branchRuntimeEnvironment.getStack().popContext().getImmutableViewOfVariables()); ReturnValues executableReturnValues = branchRuntimeEnvironment.removeReturnValues(); String branchResult = executableReturnValues.getResult(); branchContextMap.put(ScoreLangConstants.BRANCH_RESULT_KEY, branchResult); branchesContext.add(branchContextMap); // up branch path branchRuntimeEnvironment.getExecutionPath().up(); fireEvent(executionRuntimeServices, branchRuntimeEnvironment, ScoreLangConstants.EVENT_BRANCH_END, "Parallel loop branch ended", LanguageEventData.StepType.STEP, nodeName, Pair.of(RuntimeConstants.BRANCH_RETURN_VALUES_KEY, executableReturnValues) ); } } private void checkExceptionInBranch(EndBranchDataContainer branch) { //first we check that no exception was thrown during the execution of the branch String branchException = branch.getException(); if (StringUtils.isNotEmpty(branchException)) { Map<String, Serializable> systemContextMap = branch.getSystemContext(); String branchId = null; if (MapUtils.isNotEmpty(systemContextMap)) { ExecutionRuntimeServices branchExecutionRuntimeServices = new SystemContext(systemContextMap); branchId = branchExecutionRuntimeServices.getBranchId(); } logger.error("There was an error running branch: " + branchId + " Error is: " + branchException); throw new RuntimeException(BRANCH_EXCEPTION_PREFIX + ": \n" + branchException); } } private void createBranch(RunEnvironment runEnv, ExecutionRuntimeServices executionRuntimeServices, String refId, Long branchBeginStep) { Map<String, Serializable> branchContext = new HashMap<>(); branchContext.put(ScoreLangConstants.RUN_ENV, runEnv); executionRuntimeServices.addBranch(branchBeginStep, refId, branchContext); } private Map<String, Serializable> convert(Map<String, Value> map) { Map<String, Serializable> result = new HashMap<>(map.size()); for (Map.Entry<String, Value> entry : map.entrySet()) { result.put(entry.getKey(), entry.getValue() == null ? null : entry.getValue().get()); } return result; } }