/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.components.switchcmp.common; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.script.ScriptEngine; import javax.script.ScriptException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.datamodel.api.DataType; import de.rcenvironment.core.scripting.ScriptingService; import de.rcenvironment.core.scripting.ScriptingUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.incubator.ServiceRegistry; import de.rcenvironment.core.utils.incubator.ServiceRegistryAccess; import de.rcenvironment.core.utils.scripting.ScriptLanguage; /** * * Checks user script for syntax errors and not allowed jython constructs. * * @author David Scholz */ public final class ScriptValidation { private static ScriptEngine engine = null; private static final Log LOGGER = LogFactory.getLog(ScriptValidation.class); private ScriptValidation() {} /** * * Script validation in GUI. * * @param callerInstance instance for service registry * @param script condition * @param inputsAndConnectionStatus name of inputs and flag indicating whether input is connected * @param inputsAndDataTypes data types of inputs (input name -> data type) * @return error message or <code>null</code> if script is valid */ public static String validateScript(String script, Map<String, Boolean> inputsAndConnectionStatus, Map<String, DataType> inputsAndDataTypes, Object callerInstance) { if (engine == null) { // create engine for use in gui ServiceRegistryAccess serviceRegistryAccess = ServiceRegistry.createAccessFor(callerInstance); ScriptingService service = serviceRegistryAccess.getService(ScriptingService.class); engine = service.createScriptEngine(ScriptLanguage.getByName(SwitchComponentConstants.SCRIPT_LANGUAGE)); } return validateScript(script, engine, inputsAndConnectionStatus, inputsAndDataTypes); } /** * * Script validation in execution. * * @param script condition * @param scriptEngine for script execution * @param inputsAndConnectionStatus name of inputs and flag indicating whether input is connected * @param inputsAndDataTypes name of inputs and their data types * @return error message or null if script is valid */ public static String validateScript(String script, ScriptEngine scriptEngine, Map<String, Boolean> inputsAndConnectionStatus, Map<String, DataType> inputsAndDataTypes) { if (script == null || script.trim().isEmpty()) { return "No condition is defined"; } String errorMessage = ""; List<String> operatorList = new ArrayList<>(Arrays.asList(SwitchComponentConstants.OPERATORS)); operatorList.addAll(Arrays.asList(SwitchComponentConstants.OPERATORS_FOR_VALIDATION)); Pattern operatorPattern = Pattern.compile(createValidationRegex(operatorList)); Matcher operatorMatcher = operatorPattern.matcher(script); DataType inputToForwardDataType = inputsAndDataTypes.get(SwitchComponentConstants.DATA_INPUT_NAME); while (operatorMatcher.find()) { if (operatorMatcher.group(0).equals(SwitchComponentConstants.DATA_INPUT_NAME) && inputToForwardDataType != null) { if (!(inputToForwardDataType.equals(DataType.Float) || inputToForwardDataType.equals(DataType.Integer) || inputToForwardDataType.equals(DataType.Boolean))) { errorMessage = appendErrorMessage(errorMessage, StringUtils.format("Data type '%s' of input '%s' not supported in script", inputToForwardDataType, SwitchComponentConstants.DATA_INPUT_NAME)); } } if (!inputsAndConnectionStatus.containsKey(operatorMatcher.group(0)) && !operatorList.contains(operatorMatcher.group(0)) && !operatorMatcher.group(0).trim().isEmpty() && !org.apache.commons.lang3.StringUtils.isNumeric(operatorMatcher.group())) { errorMessage = appendErrorMessage(errorMessage, StringUtils.format("'%s' is not defined", operatorMatcher.group(0))); } if (inputsAndConnectionStatus.containsKey(operatorMatcher.group(0)) && !inputsAndConnectionStatus.get(operatorMatcher.group(0))) { errorMessage = appendErrorMessage(errorMessage, StringUtils.format("'%s' is not connected", operatorMatcher.group(0))); } } for (Entry<String, DataType> entry : inputsAndDataTypes.entrySet()) { switch (entry.getValue()) { case Integer: script = script.replace(entry.getKey(), "11"); break; case Float: script = script.replace(entry.getKey(), "11.1"); break; case Boolean: script = script.replace(entry.getKey(), "True"); break; default: break; } } 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) { String evalScript = "if " + script + ":\n returnValue=True\nelse:\n returnValue=False"; scriptEngine.eval(evalScript); // test for jython specific syntax errors } } catch (ScriptException e) { // generate own exception message to hide the use of jython errorMessage = appendErrorMessage(errorMessage, "Syntax error: mismatched input " + "at line number " + e.getLineNumber() + " at column number " + e.getColumnNumber()); } return errorMessage; } private static String appendErrorMessage(String errorMessage, String errorMessageToAppend) { if (!errorMessage.isEmpty()) { errorMessage += "\n"; } errorMessage += errorMessageToAppend; return errorMessage; } private static String createValidationRegex(List<String> operatorList) { StringBuilder op = new StringBuilder(); op.append("("); for (String operator : operatorList) { if (operator.equals("<") || operator.equals(">")) { op.append(operator + "(?!=)"); } else { op.append(operator); } op.append("|"); } op.append("\\b\\w+\\b"); op.append(")"); try { Pattern.compile(op.toString()); } catch (PatternSyntaxException e) { LOGGER.error("Invalid Regex!", e); } return op.toString(); } }