/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.operator; import groovy.lang.GroovyShell; import groovy.lang.Script; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import com.rapidminer.operator.ports.InputPortExtender; import com.rapidminer.operator.ports.OutputPortExtender; import com.rapidminer.parameter.ParameterType; import com.rapidminer.parameter.ParameterTypeBoolean; import com.rapidminer.parameter.ParameterTypeText; import com.rapidminer.parameter.TextType; import com.rapidminer.tools.plugin.Plugin; /** <p>This operator can be used to execute arbitrary Groovy scripts. * This basically means that analysts can write their own operators * directly within the process by specifiying Java code and / or a * Groovy script which will be interpreted and executed during * process runtime. For a complete reference of Groovy scripting * please refer to http://groovy.codehaus.org/.</p> * * <p>In addition to the usual scripting code elements from Groovy, * the RapidMiner scripting operator defines some special scripting * elements:</p> * <ul> * <li>If you use the standard <em>imports</em>, all important types like * Example, ExampleSet, Attribute, Operator etc. as well as the * most important Java types like collections etc. are automatically * imported and can directly be used within the script. Hence, there is * no need for importing them in your script. However, you can * of course import any other class you want and use this in your script.</li> * <li>The <em>current operator</em> (the scripting operator for which you * define the script) is referenced by <code>operator</code>.<br /> * Example: <code>operator.log("text")</code></li> * <li>All <em>operator methods</em> like <code>log</code> (see above), * accessing the input or the complete process can directly be used by * writing a preceding <code>operator</code>.<br /> * Example: <code>operator.getProcess()</code></li> * <li><em>Input of the operator</em> can be retrieved via the input method * getInput(Class) of the surrounding operator.<br /> * Example: <code>ExampleSet exampleSet = operator.getInput(ExampleSet.class)</code></li> * <li>You can <em>iterate over examples</em> with the following construct:<br /> * <code>for (Example example : exampleSet) { ... }</code></li> * <li>You can <em>retrieve example values</em> with the shortcut<br /> * <code>String value = example["attribute_name"];</code> or <br /> * <code>double value = example["attribute_name"];</code></li> * <li>You can <em>set example values</em> with * <code>example["attribute_name"] = "value";</code> or <br /> * <code>example["attribute_name"] = 5.7;</code></li> * </ul> * * <p><em>Note:</em> Scripts written for this operator may access Java code. * Scripts may hence become incompatible in future releases of RapidMiner.</p> * * @author Simon Fischer, Ingo Mierswa */ public class ScriptingOperator extends Operator { private InputPortExtender inExtender = new InputPortExtender("input", getInputPorts()); private OutputPortExtender outExtender = new OutputPortExtender("output", getOutputPorts()); public static final String PARAMETER_SCRIPT = "script"; public static final String PARAMETER_STANDARD_IMPORTS = "standard_imports"; public ScriptingOperator(OperatorDescription description) { super(description); inExtender.start(); outExtender.start(); } @Override public void doWork() throws OperatorException { String script = getParameterAsString(PARAMETER_SCRIPT); if (getParameterAsBoolean(PARAMETER_STANDARD_IMPORTS)) { StringBuffer imports = new StringBuffer(); imports.append("import com.rapidminer.example.*;\n"); imports.append("import com.rapidminer.example.set.*;\n"); imports.append("import com.rapidminer.example.table.*;\n"); imports.append("import com.rapidminer.operator.*;\n"); imports.append("import com.rapidminer.tools.Tools;\n"); imports.append("import java.util.*;\n"); script = imports.toString() + script; } Object result; try { GroovyShell shell = new GroovyShell(Plugin.getMajorClassLoader()); //GroovyShell shell = new GroovyShell(ScriptingOperator.class.getClassLoader()); List<IOObject> input = inExtender.getData(false); shell.setVariable("input", input); shell.setVariable("operator", this); Script parsedScript = shell.parse(script); result = parsedScript.run(); } catch (Throwable e) { throw new UserError(this, e, 945, "Groovy", e); } if (result instanceof Object[]) { outExtender.deliver(Arrays.asList((IOObject[])result)); } else if (result instanceof List) { List<IOObject> results = new LinkedList<IOObject>(); for (Object single : (List)result) { if (single instanceof IOObject) { results.add((IOObject)single); } else { getLogger().warning("Unknown result type: "+single); } } outExtender.deliver(results); } else { if (result != null) { if (result instanceof IOObject) { outExtender.deliver(Collections.singletonList((IOObject)result));; } else { getLogger().warning("Unknown result: "+result.getClass()+": "+result); } } } } @Override public List<ParameterType> getParameterTypes() { List<ParameterType> types = super.getParameterTypes(); ParameterType type = new ParameterTypeText(PARAMETER_SCRIPT, "The script to execute.", TextType.JAVA, false); type.setExpert(false); types.add(type); types.add(new ParameterTypeBoolean(PARAMETER_STANDARD_IMPORTS, "Indicates if standard imports for examples and attributes etc. should be automatically generated.", true)); return types; } }