INT-2177 - Added python support and related refactoring

This commit is contained in:
David Turanski
2011-10-14 09:57:21 -04:00
committed by Mark Fisher
parent 1d80d34225
commit a0e7b3cd78
21 changed files with 449 additions and 117 deletions

View File

@@ -35,5 +35,6 @@ public interface ScriptExecutor {
* @param variables -bind variable
* @return
*/
public abstract Object executeScript(ScriptSource scriptSource,Map<String,Object> variables);
public abstract Object executeScript(ScriptSource scriptSource,Map<String,Object> variables);
}

View File

@@ -19,7 +19,7 @@ package org.springframework.integration.scripting.config.jsr223;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.scripting.config.AbstractScriptParser;
import org.springframework.integration.scripting.jsr223.DefaultScriptExecutor;
import org.springframework.integration.scripting.jsr223.ScriptExecutorFactory;
import org.springframework.util.Assert;
import org.w3c.dom.Element;
@@ -47,7 +47,7 @@ public class ScriptParser extends AbstractScriptParser {
protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext){
String language = element.getAttribute(LANGUAGE_ATTRIBUTE);
Assert.hasLength(language, "Attribute " + LANGUAGE_ATTRIBUTE + " is required");
builder.addConstructorArgValue(new DefaultScriptExecutor(language));
builder.addConstructorArgValue(ScriptExecutorFactory.getScriptExecutor(language));
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.scripting.jsr223;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.scripting.ScriptExecutor;
import org.springframework.scripting.ScriptSource;
import org.springframework.util.Assert;
/**
* Base Class for {@link ScriptExecutor}
*
* @author David Turanski
* @author Mark Fisher
* @since 2.1
*/
abstract class AbstractScriptExecutor implements ScriptExecutor {
protected final Log logger = LogFactory.getLog(this.getClass());
protected final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
protected final String language;
public AbstractScriptExecutor(String language) {
Assert.hasText(language, "language must not be empty");
this.language = language;
if (logger.isDebugEnabled()) {
logger.debug("using script engine : " + scriptEngineManager.getEngineByName(language).getFactory().getEngineName());
for (String name:scriptEngineManager.getEngineByName(language).getFactory().getNames()){
logger.debug("name=" + name);
}
}
}
public Object executeScript(ScriptSource scriptSource) {
return this.executeScript(scriptSource, null);
}
public Object executeScript(ScriptSource scriptSource, Map<String, Object> variables) {
Object result = null;
ScriptEngine scriptEngine = this.scriptEngineManager.getEngineByName(this.language);
try {
if (variables != null) {
for (Entry<String, Object> entry : variables.entrySet()) {
scriptEngine.put(entry.getKey(), entry.getValue());
}
}
String script = scriptSource.getScriptAsString();
Date start = new Date();
if (logger.isDebugEnabled()) {
logger.debug("executing script: " + script);
}
result = scriptEngine.eval(script);
result = postProcess(result, scriptEngine, script);
if (logger.isDebugEnabled()) {
logger.debug("script executed in " + (new Date().getTime() - start.getTime()) + " ms");
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
catch (ScriptException e) {
throw new RuntimeException(e);
}
return result;
}
/**
* Subclasses may implement this to provide any special handling required
* @param result
* @param scriptEngine
* @param script
* @return modified result
*/
protected abstract Object postProcess(Object result, ScriptEngine scriptEngine, String script);
}

View File

@@ -12,88 +12,36 @@
*/
package org.springframework.integration.scripting.jsr223;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.scripting.ScriptExecutor;
import org.springframework.scripting.ScriptSource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Executes JSR223 scripts
* Default implementation of the {@link ScriptExecutor}
*
* @author David Turanski
* @author Mark Fisher
* @since 2.1
*/
public class DefaultScriptExecutor implements ScriptExecutor {
private static final Log logger = LogFactory.getLog(DefaultScriptExecutor.class);
static {
if (ClassUtils.isPresent("org.jruby.embed.jsr223.JRubyEngine", System.class.getClassLoader())) {
System.setProperty("org.jruby.embed.localvariable.behavior", "transient");
System.setProperty("org.jruby.embed.localcontext.scope", "threadsafe");
}
}
private final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
private final String language;
class DefaultScriptExecutor extends AbstractScriptExecutor {
/**
* Create a DefaultScriptExceutor for the specified language name (JSR233 alias).
* Create a DefaultScriptExceutor for the specified language name (JSR233
* alias).
*/
public DefaultScriptExecutor(String language) {
Assert.hasText(language, "language must not be empty");
this.language = language;
if (logger.isDebugEnabled()) {
logger.debug("using script engine : " + scriptEngineManager.getEngineByName(language).getFactory().getEngineName());
}
super(language);
}
public Object executeScript(ScriptSource scriptSource) {
return this.executeScript(scriptSource, null);
}
public Object executeScript(ScriptSource scriptSource, Map<String, Object> variables) {
Object result = null;
ScriptEngine scriptEngine = this.scriptEngineManager.getEngineByName(this.language);
try {
if (variables != null) {
for (Entry<String, Object> entry : variables.entrySet()) {
scriptEngine.put(entry.getKey(), entry.getValue());
}
}
String script = scriptSource.getScriptAsString();
Date start = new Date();
if (logger.isDebugEnabled()) {
logger.debug("executing script: " + script);
}
result = scriptEngine.eval(script);
if (logger.isDebugEnabled()) {
logger.debug("script executed in " + (new Date().getTime() - start.getTime()) + " ms");
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
catch (ScriptException e) {
throw new RuntimeException(e);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.integration.scripting.jsr223.AbstractScriptExecutor
* #postProcess(java.lang.Object, javax.script.ScriptEngine,
* java.lang.String)
*/
@Override
protected Object postProcess(Object result, ScriptEngine scriptEngine, String script) {
return result;
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.scripting.jsr223;
import javax.script.ScriptEngine;
import org.springframework.integration.scripting.ScriptExecutor;
/**
* A {@link ScriptExecutor} that implements special handling required for Python to emulate behavior similar to other JSR223 scripting languages.
* <p>
* Script evaluation using the Jython implementation results in a <code>null</code> return value for normal variable expressions such as
* <code>x=2</code>. As a work around, it is necessary to get the value of 'x' explicitly following the script evaluation. This class performs
* simple parsing on the last line of the script to obtain the variable name, if any, and return its value.
*
* @author David Turanski
* @since 2.1
*
*/
class PythonScriptExecutor extends AbstractScriptExecutor {
/**
* @param language
*/
public PythonScriptExecutor() {
super("python");
}
/* (non-Javadoc)
* @see org.springframework.integration.scripting.jsr223.AbstractScriptExecutor#postProcess(java.lang.Object, javax.script.ScriptEngine, java.lang.String)
*/
@Override
protected Object postProcess(Object result, ScriptEngine scriptEngine, String script) {
Object newResult= result;
if (newResult == null) {
String returnVariableName = PythonVariableParser.parseReturnVariable(script);
newResult = scriptEngine.get(returnVariableName);
}
return newResult;
}
public static class PythonVariableParser {
public static String parseReturnVariable(String script){
String[] lines = script.trim().split("\n");
String lastLine = lines[lines.length -1];
String[] tokens = lastLine.split("=");
return tokens[0].trim();
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.scripting.jsr223;
import org.springframework.util.ClassUtils;
/**
* @author David Turanski
* @since 2.1
*
*/
class RubyScriptExecutor extends DefaultScriptExecutor {
static {
if (ClassUtils.isPresent("org.jruby.embed.jsr223.JRubyEngine", System.class.getClassLoader())) {
System.setProperty("org.jruby.embed.localvariable.behavior", "transient");
System.setProperty("org.jruby.embed.localcontext.scope", "threadsafe");
}
}
public RubyScriptExecutor() {
super("ruby");
}
}

View File

@@ -28,7 +28,7 @@ import org.springframework.util.Assert;
*/
public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor<Object> {
private final DefaultScriptExecutor scriptExecutor;
private final ScriptExecutor scriptExecutor;
private volatile ScriptSource scriptSource;
@@ -37,7 +37,7 @@ public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMess
* @param scriptSource
* @param scriptExecutor
*/
public ScriptExecutingMessageProcessor(ScriptSource scriptSource, DefaultScriptExecutor scriptExecutor) {
public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptExecutor scriptExecutor) {
super();
this.scriptSource = scriptSource;
this.scriptExecutor = scriptExecutor;
@@ -48,7 +48,7 @@ public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMess
* @param scriptSource
* @param scriptExecutor
*/
public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptVariableGenerator scriptVariableGenerator,DefaultScriptExecutor scriptExecutor) {
public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptVariableGenerator scriptVariableGenerator, ScriptExecutor scriptExecutor) {
super(scriptVariableGenerator);
this.scriptSource = scriptSource;
this.scriptExecutor = scriptExecutor;
@@ -60,7 +60,7 @@ public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMess
* @param scriptExecutor
* @param variables
*/
public ScriptExecutingMessageProcessor(ScriptSource scriptSource,DefaultScriptExecutor scriptExecutor,Map<String,Object> variables ) {
public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptExecutor scriptExecutor,Map<String,Object> variables ) {
super(new DefaultScriptVariableGenerator(variables));
this.scriptSource = scriptSource;
this.scriptExecutor = scriptExecutor;

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.scripting.jsr223;
import org.springframework.integration.scripting.ScriptExecutor;
/**
* @author David Turanski
* @since 2.1
*/
public class ScriptExecutorFactory {
private ScriptExecutorFactory(){};
public static ScriptExecutor getScriptExecutor(String language) {
if (language.equalsIgnoreCase("python") || language.equalsIgnoreCase("jython")){
return new PythonScriptExecutor();
}
else if (language.equalsIgnoreCase("ruby") || language.equalsIgnoreCase("jruby")) {
return new RubyScriptExecutor();
}
return new DefaultScriptExecutor(language);
}
}

View File

@@ -26,6 +26,13 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="return" use="optional">
<xsd:annotation>
<xsd:documentation>
The script variable to return as a result of the evaluation (Optional).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>