/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.components.switchcmp.execution; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import javax.script.ScriptEngine; import javax.script.ScriptException; import org.apache.commons.lang3.text.WordUtils; import de.rcenvironment.components.switchcmp.common.ScriptValidation; import de.rcenvironment.components.switchcmp.common.SwitchComponentConstants; import de.rcenvironment.components.switchcmp.common.SwitchComponentHistoryDataItem; import de.rcenvironment.core.component.api.ComponentConstants; import de.rcenvironment.core.component.api.ComponentException; import de.rcenvironment.core.component.execution.api.ComponentContext; import de.rcenvironment.core.component.model.spi.DefaultComponent; import de.rcenvironment.core.datamodel.api.DataType; import de.rcenvironment.core.datamodel.api.TypedDatum; import de.rcenvironment.core.scripting.ScriptingService; import de.rcenvironment.core.scripting.ScriptingUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.scripting.ScriptLanguage; /** * Switch component. * * @author David Scholz * @author Doreen Seider */ public class SwitchComponent extends DefaultComponent { protected static ScriptingService scriptingService; private static final String QUOTE = "\\\\Q"; private static final String ENDQUOTE = "\\\\E"; private ScriptLanguage scriptLanguage; private ScriptEngine engine; private String condition; private CloseOutputBehavior closeOutputBehavior; private ComponentContext componentContext; private SwitchComponentHistoryDataItem historyDataItem; /** * Whether outputs should be closed. * * @author Doreen Seider */ private enum CloseOutputBehavior { NeverCloseOutputs, CloseOutputsOnTrue, CloseOutputsOnFalse; } @Override public void setComponentContext(ComponentContext componentContext) { this.componentContext = componentContext; } @Override public void start() throws ComponentException { condition = componentContext.getConfigurationValue(SwitchComponentConstants.CONDITION_KEY); closeOutputBehavior = getCloseOutputBehavior(); scriptLanguage = ScriptLanguage.getByName(SwitchComponentConstants.SCRIPT_LANGUAGE); scriptingService = componentContext.getService(ScriptingService.class); engine = scriptingService.createScriptEngine(scriptLanguage); String errorMessage = ScriptValidation.validateScript(condition, engine, getInputAndConnectionStatus(), getInputsAndDataTypes()); if (!errorMessage.isEmpty()) { // validation before workflowstart throw new ComponentException(errorMessage); } } // Use one key for each enum value in component config map to make use of the property handling in the GUI provided by // WorkflowNodePropertySection. It can't map a radio button group to enums. (But it can map one single radio button to one config key. private CloseOutputBehavior getCloseOutputBehavior() { if (Boolean.valueOf(componentContext.getConfigurationValue(SwitchComponentConstants.CLOSE_OUTPUTS_ON_TRUE_KEY))) { return CloseOutputBehavior.CloseOutputsOnTrue; } else if (Boolean.valueOf(componentContext.getConfigurationValue(SwitchComponentConstants.CLOSE_OUTPUTS_ON_FALSE_KEY))) { return CloseOutputBehavior.CloseOutputsOnFalse; } else { return CloseOutputBehavior.NeverCloseOutputs; } } @Override public void processInputs() throws ComponentException { initializeNewHistoryDataItem(); String conditionWithActualValues = condition; TypedDatum switchDatum = componentContext.readInput(SwitchComponentConstants.DATA_INPUT_NAME); for (String inputName : componentContext.getInputsWithDatum()) { TypedDatum datum = componentContext.readInput(inputName); if (datum.getDataType().equals(DataType.Float) || datum.getDataType().equals(DataType.Integer)) { conditionWithActualValues = conditionWithActualValues.replace(inputName, Pattern.quote(datum.toString()) .replaceAll(QUOTE, "")).replaceAll(ENDQUOTE, ""); } if (datum.getDataType().equals(DataType.Boolean)) { conditionWithActualValues = conditionWithActualValues.replace(inputName, WordUtils.capitalize(Pattern.quote(datum.toString()) .replaceAll(QUOTE, ""))).replaceAll(ENDQUOTE, ""); } } String evalScript = "if " + conditionWithActualValues + ":\n returnValue=True\nelse:\n returnValue=False"; Object returnValue; try { // As the Jython script engine is not thread safe (console outputs of multiple script // executions are mixed), we must ensure that at most one script is executed at the same // time synchronized (ScriptingUtils.SCRIPT_EVAL_LOCK_OBJECT) { engine.eval(evalScript); returnValue = engine.get("returnValue"); } } catch (ScriptException e) { throw new ComponentException(StringUtils.format("Failed to interpret condition '%s': %s", conditionWithActualValues, e.getMessage()), e); // should never happen } componentContext.getLog().componentInfo(StringUtils.format("Evaluated '%s' -> %b", conditionWithActualValues, returnValue)); if (historyDataItem != null) { historyDataItem.setActualCondition(StringUtils.format("%s -> %b", conditionWithActualValues, returnValue)); historyDataItem.setConditionPattern(condition); } String outputName; if ((Boolean) returnValue) { componentContext.writeOutput(SwitchComponentConstants.TRUE_OUTPUT, switchDatum); if (closeOutputBehavior == CloseOutputBehavior.CloseOutputsOnTrue) { componentContext.closeAllOutputs(); } outputName = SwitchComponentConstants.TRUE_OUTPUT; } else { componentContext.writeOutput(SwitchComponentConstants.FALSE_OUTPUT, switchDatum); if (closeOutputBehavior == CloseOutputBehavior.CloseOutputsOnFalse) { componentContext.closeAllOutputs(); } outputName = SwitchComponentConstants.FALSE_OUTPUT; } componentContext.getLog().componentInfo(StringUtils.format("Wrote to '%s': %s", outputName, switchDatum)); writeFinalHistoryDataItem(); } @Override public void completeStartOrProcessInputsAfterFailure() throws ComponentException { writeFinalHistoryDataItem(); } private void initializeNewHistoryDataItem() { if (Boolean.valueOf(componentContext.getConfigurationValue(ComponentConstants.CONFIG_KEY_STORE_DATA_ITEM))) { historyDataItem = new SwitchComponentHistoryDataItem(SwitchComponentConstants.COMPONENT_ID); } } private void writeFinalHistoryDataItem() { if (Boolean.valueOf(componentContext.getConfigurationValue(ComponentConstants.CONFIG_KEY_STORE_DATA_ITEM))) { componentContext.writeFinalHistoryDataItem(historyDataItem); } } private Map<String, Boolean> getInputAndConnectionStatus() { Map<String, Boolean> inputs = new HashMap<>(); for (String name : componentContext.getInputs()) { inputs.put(name, true); } for (String name : componentContext.getInputsNotConnected()) { inputs.put(name, false); } return inputs; } private Map<String, DataType> getInputsAndDataTypes() { Map<String, DataType> inputs = new HashMap<>(); for (String name : componentContext.getInputs()) { inputs.put(name, componentContext.getInputDataType(name)); } for (String name : componentContext.getInputsNotConnected()) { inputs.put(name, componentContext.getInputDataType(name)); } return inputs; } }