/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.components.script.execution.python.internal; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.script.ScriptException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.components.script.common.ScriptComponentHistoryDataItem; import de.rcenvironment.components.script.common.registry.ScriptExecutor; import de.rcenvironment.components.script.execution.DefaultScriptExecutor; import de.rcenvironment.core.component.api.ComponentException; import de.rcenvironment.core.component.datamanagement.api.ComponentDataManagementService; import de.rcenvironment.core.component.execution.api.ComponentContext; import de.rcenvironment.core.configuration.ConfigurationService; import de.rcenvironment.core.datamodel.api.DataType; import de.rcenvironment.core.datamodel.api.TypedDatum; import de.rcenvironment.core.datamodel.api.TypedDatumFactory; import de.rcenvironment.core.datamodel.api.TypedDatumService; import de.rcenvironment.core.datamodel.types.api.VectorTD; import de.rcenvironment.core.scripting.ScriptingService; import de.rcenvironment.core.scripting.ScriptingUtils; import de.rcenvironment.core.scripting.python.PythonComponentConstants; import de.rcenvironment.core.scripting.python.PythonScriptContext; import de.rcenvironment.core.scripting.python.PythonScriptEngine; import de.rcenvironment.core.utils.common.OSFamily; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.scripting.ScriptLanguage; /** * * Implementation of {@link ScriptExecutor} to execute python scripts. This is needed because Python is not part of the default ScriptEngine * and must be implemented manually. For this we require some different code for the {@link ScriptExecutor} methods. * * @author Sascha Zur * @author Jascha Riedel (#14029) */ public class PythonScriptExecutor extends DefaultScriptExecutor { private static final String NOT_VALUE_UUID = "not_a_value_7fdc603e"; private static final String OS = "os"; private PythonScriptContext scriptContext; private Log log = LogFactory.getLog(PythonScriptExecutor.class); @Override public boolean prepareExecutor(ComponentContext compCtx) throws ComponentException { super.prepareExecutor(compCtx); componentContext = compCtx; String pythonInstallation = componentContext.getConfigurationValue(PythonComponentConstants.PYTHON_INSTALLATION); if (pythonInstallation == null || pythonInstallation.isEmpty()) { throw new ComponentException("No Python installation specified."); } scriptContext = new PythonScriptContext(); scriptContext.setAttribute(PythonComponentConstants.PYTHON_INSTALLATION, pythonInstallation, 0); scriptContext.setAttribute(OS, OSFamily.getLocal(), 0); scriptContext.setAttribute(PythonComponentConstants.COMPONENT_CONTEXT, componentContext, 0); stateMap = new HashMap<>(); scriptingService = compCtx.getService(ScriptingService.class); return true; } @Override public void prepareOutputForRun() {} @Override public void prepareNewRun(ScriptLanguage scriptLanguage, String userScript, ScriptComponentHistoryDataItem dataItem) throws ComponentException { historyDataItem = dataItem; scriptEngine = scriptingService.createScriptEngine(scriptLanguage); wrappingScript = userScript; if (wrappingScript == null || wrappingScript.length() == 0) { throw new ComponentException("No Python script configured"); } scriptEngine.setContext(scriptContext); scriptContext.removeAttribute(PythonComponentConstants.STATE_MAP, 0); scriptContext.setAttribute(PythonComponentConstants.STATE_MAP, stateMap, 0); scriptContext.removeAttribute(PythonComponentConstants.RUN_NUMBER, 0); scriptContext.setAttribute(PythonComponentConstants.RUN_NUMBER, getCurrentRunNumber(), 0); ((PythonScriptEngine) scriptEngine).createNewExecutor(historyDataItem); typedDatumFactory = componentContext.getService(TypedDatumService.class).getFactory(); } @Override public void runScript() throws ComponentException { int exitCode = 0; try { // Executing script here componentContext.announceExternalProgramStart(); exitCode = (Integer) scriptEngine.eval(wrappingScript); } catch (ScriptException e) { throw new ComponentException("Failed to execute script", e); } finally { componentContext.announceExternalProgramTermination(); } if (exitCode != 0) { throw new ComponentException("Failed to execute script; exit code: " + exitCode); } } @Override public boolean postRun() throws ComponentException { TypedDatumFactory factory = componentContext.getService(TypedDatumService.class).getFactory(); for (String outputName : componentContext.getOutputs()) { DataType type = componentContext.getOutputDataType(outputName); @SuppressWarnings("unchecked") List<Object> resultList = (List<Object>) scriptEngine.get(outputName); TypedDatum outputValue = null; if (scriptEngine.get(outputName) != null) { for (Object o : resultList) { if (o != null && !String.valueOf(o).equals(NOT_VALUE_UUID)) { switch (type) { case ShortText: outputValue = factory.createShortText(String.valueOf(o)); break; case Boolean: outputValue = convertBoolean(factory, o); break; case Float: try { outputValue = factory.createFloat(Double.parseDouble(String.valueOf(o))); } catch (NumberFormatException e) { throw new ComponentException(StringUtils.format("Failed to parse output value '%s' to data type Float." + " Possible reasons (not restricted): Ouput value too big (max. %.1e)," + " or ouput value contains non numeric characters.", o.toString(), Double.MAX_VALUE)); } break; case Integer: try { outputValue = factory.createInteger(Long.parseLong(String.valueOf(o))); } catch (NumberFormatException e) { throw new ComponentException(StringUtils.format("Failed to parse output value '%s' to data type Integer." + " Possible reasons (not restricted): Ouput value too big (max. 2E" + Long.toBinaryString(Long.MAX_VALUE).length() + " - 1)," + " or ouput value contains non numeric characters.", o.toString())); } break; case FileReference: outputValue = handleFileOrDirectoryOutput(outputName, outputValue, "file", o); break; case DirectoryReference: outputValue = handleFileOrDirectoryOutput(outputName, outputValue, "directory", o); break; case Empty: outputValue = factory.createEmpty(); break; case Vector: if (!(o instanceof List)) { throw new ComponentException( StringUtils.format("Value \"%s\" of output \"%s\" is not of type vector.", o, outputName)); } List<Object> resultVector = (List<Object>) o; VectorTD vector = factory.createVector(resultVector.size()); int index = 0; for (Object element : resultVector) { if (element instanceof List) { throw new ComponentException(StringUtils .format("Value for endpoint %s was a matrix, but endpoint is of type Vector", outputName)); } double convertedValue = 0; if (element == null) { throw new ComponentException(StringUtils .format("Value \"None\" of cell %s is not valid for type Vector \"%s\"", index, outputName)); } else if (element instanceof Integer) { convertedValue = (Integer) element; } else if (element instanceof Double) { convertedValue = (Double) element; } else if (element instanceof String && ((String) element).equals("+Infinity")) { convertedValue = Double.POSITIVE_INFINITY; } vector.setFloatTDForElement(factory.createFloat(convertedValue), index); index++; } outputValue = vector; break; case Matrix: outputValue = ScriptingUtils.createResultMatrix(o, outputName); break; case SmallTable: if (!(o instanceof List)) { throw new ComponentException( StringUtils.format("Value \"%s\" of output \"%s\" is not of type small table.", o, outputName)); } List<Object> rowArray = (List<Object>) o; TypedDatum[][] result = new TypedDatum[rowArray.size()][]; if (rowArray.size() > 0 && rowArray.get(0).getClass().getName().equals(ArrayList.class.getName())) { int i = 0; int size = 0; Object first = ""; for (Object columnObject : rowArray) { if (!(columnObject instanceof List)) { throw new ComponentException( StringUtils.format("Value \"%s\" of output \"%s\" is not of type small table.", columnObject, outputName)); } List<Object> columnArray = (List<Object>) columnObject; if (size == 0) { first = columnObject; size = columnArray.size(); } if (size != columnArray.size()) { throw new ComponentException(StringUtils.format( "Each row must have the same number of elements in a small table. " + "Element count of \"%s\" and \"%s\" does not match.", first, columnObject)); } if (columnArray.size() == 0) { result[i] = new TypedDatum[1]; result[i][0] = factory.createEmpty(); } else { result[i] = new TypedDatum[columnArray.size()]; } int j = 0; for (Object element : columnArray) { result[i][j++] = getTypedDatum(element); } i++; } outputValue = factory.createSmallTable(result); } else { int i = 0; for (Object element : rowArray) { result[i] = new TypedDatum[1]; result[i][0] = getTypedDatum(element); i++; } outputValue = factory.createSmallTable(result); } break; default: outputValue = factory.createShortText(o.toString()); // should not happen } componentContext.writeOutput(outputName, outputValue); } else if (String.valueOf(o).equals(NOT_VALUE_UUID)) { outputValue = factory.createNotAValue(); // "not a value" value componentContext.writeOutput(outputName, outputValue); } } } } stateMap = ((PythonScriptEngine) scriptEngine).getStateOutput(); for (String outputName : ((PythonScriptEngine) scriptEngine).getCloseOutputChannelsList()) { componentContext.closeOutput(outputName); } ((PythonScriptEngine) scriptEngine).dispose(); return true; } private TypedDatum convertBoolean(TypedDatumFactory factory, Object o) throws ComponentException { String stringValue = o.toString(); boolean isNumber = true; TypedDatum outputValue = null; try { float numberValue = Float.parseFloat(stringValue); if (Math.abs(numberValue) > 0) { outputValue = factory.createBoolean(true); } else { outputValue = factory.createBoolean(false); } } catch (NumberFormatException e) { isNumber = false; } if (!isNumber && (stringValue.equalsIgnoreCase("0") || stringValue.equalsIgnoreCase("0L") || stringValue.equalsIgnoreCase("0.0") || stringValue.equalsIgnoreCase("0j") || stringValue.equalsIgnoreCase("()") || stringValue.equalsIgnoreCase("[]") || stringValue.isEmpty() || stringValue.equalsIgnoreCase("{}") || stringValue.equalsIgnoreCase("false") || stringValue.equalsIgnoreCase("none"))) { outputValue = factory.createBoolean(false); } else if (!isNumber) { outputValue = factory.createBoolean(true); } return outputValue; } private TypedDatum handleFileOrDirectoryOutput(String outputName, TypedDatum outputValue, String type, Object o) throws ComponentException { try { File file = new File(String.valueOf(o)); if (!file.isAbsolute()) { file = new File(((PythonScriptEngine) scriptEngine).getExecutor().getWorkDir(), String.valueOf(o)); } if (file.exists()) { if (type.equals("directory")) { outputValue = componentContext.getService(ComponentDataManagementService.class).createDirectoryReferenceTDFromLocalDirectory( componentContext, file, file.getName()); } else { outputValue = componentContext.getService(ComponentDataManagementService.class).createFileReferenceTDFromLocalFile( componentContext, file, file.getName()); } if (file.getAbsolutePath().startsWith( componentContext.getService(ConfigurationService.class) .getParentTempDirectoryRoot().getAbsolutePath())) { tempFiles.add(file); } } else { throw new ComponentException(StringUtils.format( "Failed to write %s to output '%s' as it does not exist: %s", type, outputName, file.getAbsolutePath())); } } catch (IOException e) { throw new ComponentException(StringUtils.format( "Failed to store %s into the data management - " + "if it is not stored in the data management, it can not be sent as output value", type), e); } return outputValue; } @Override public void deleteTempFiles() { super.deleteTempFiles(); if (scriptEngine != null) { ((PythonScriptEngine) scriptEngine).dispose(); } } @Override public void cancelScript() { if (scriptEngine == null) { // FIXME we need to delay the cancellation request, currently it is simple ignored in this case log.error("Cannot cancel the execution, as the script engine (Script Component) is not propertly prepared."); return; } ((PythonScriptEngine) scriptEngine).cancel(); } @Override public boolean isCancelable() { return true; } }