Add support for Kotlin JSR223 scripts (#2898)
* Add support for Kotlin JSR223 scripts * Add required Kotlin dependencies into the `spring-integration-scripting` module * Introduce `KotlinScriptExecutor` to interact with the `KotlinJsr223JvmLocalScriptEngineFactory` directly since there is no `META-INF/services/javax.script.ScriptEngineFactory` file in the Kotlin * Also set an `idea.use.native.fs.for.win` system property to `false` in this class to disable check for native support on Windows. (Might be removed in future Kotlin versions) * Move `ScriptParser.getLanguageFromFileExtension()` logic into the `ScriptExecutorFactory.deriveLanguageFromFileExtension()` since the same one must be applied in the `DslScriptExecutingMessageProcessor`, too. * Modify tests to reflect Kotlin support * Fix some test scripts to their official extensions * * Add JavaDocs * Polishing according Sonar objections
This commit is contained in:
committed by
Gary Russell
parent
9cc0cbe3bd
commit
7dff1d5416
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.integration.handler.MessageProcessor;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -42,9 +43,9 @@ public abstract class AbstractScriptExecutingMessageProcessor<T>
|
||||
|
||||
private final ScriptVariableGenerator scriptVariableGenerator;
|
||||
|
||||
protected volatile ClassLoader beanClassLoader;
|
||||
protected ClassLoader beanClassLoader;
|
||||
|
||||
protected volatile BeanFactory beanFactory;
|
||||
protected BeanFactory beanFactory;
|
||||
|
||||
protected AbstractScriptExecutingMessageProcessor() {
|
||||
this(new DefaultScriptVariableGenerator());
|
||||
@@ -70,6 +71,7 @@ public abstract class AbstractScriptExecutingMessageProcessor<T>
|
||||
* Executes the script and returns the result.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public final T processMessage(Message<?> message) {
|
||||
try {
|
||||
ScriptSource source = getScriptSource(message);
|
||||
@@ -96,6 +98,7 @@ public abstract class AbstractScriptExecutingMessageProcessor<T>
|
||||
* @param variables The variables.
|
||||
* @return The result of the execution.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract T executeScript(ScriptSource scriptSource, Map<String, Object> variables);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,9 +24,13 @@ import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.scripting.support.ResourceScriptSource;
|
||||
|
||||
/**
|
||||
* A {@link ScriptSource} implementation, which caches a script string and refreshes it from the
|
||||
* target file (if modified) according the provided {@link #refreshDelay}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class RefreshableResourceScriptSource implements ScriptSource {
|
||||
|
||||
@@ -18,29 +18,37 @@ package org.springframework.integration.scripting;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scripting.ScriptSource;
|
||||
|
||||
/**
|
||||
* A script evaluation abstraction against {@link ScriptSource} and optional binding {@code variables}.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ScriptExecutor {
|
||||
|
||||
/**
|
||||
* @param scriptSource The script source.
|
||||
* @return The result of the execution.
|
||||
*/
|
||||
default Object executeScript(ScriptSource scriptSource) {
|
||||
return executeScript(scriptSource, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a script from the provided {@link ScriptSource} with an optional binding {@code variables}.
|
||||
* @param scriptSource The script source.
|
||||
* @param variables The variables.
|
||||
* @return The result of the execution.
|
||||
*/
|
||||
Object executeScript(ScriptSource scriptSource, Map<String, Object> variables);
|
||||
@Nullable
|
||||
Object executeScript(ScriptSource scriptSource, @Nullable Map<String, Object> variables);
|
||||
|
||||
/**
|
||||
* Execute a script from the provided {@link ScriptSource}
|
||||
* @param scriptSource The script source.
|
||||
* @return The result of the execution.
|
||||
*/
|
||||
@Nullable
|
||||
default Object executeScript(ScriptSource scriptSource) {
|
||||
return executeScript(scriptSource, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package org.springframework.integration.scripting.config.jsr223;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
@@ -29,8 +26,11 @@ import org.springframework.integration.scripting.jsr223.ScriptExecutorFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link AbstractScriptParser} parser extension for the {@code <int-script:script>} tag.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ScriptParser extends AbstractScriptParser {
|
||||
@@ -50,38 +50,12 @@ public class ScriptParser extends AbstractScriptParser {
|
||||
if (!StringUtils.hasText(scriptLocation)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"An inline script requires the '" + LANGUAGE_ATTRIBUTE + "' attribute.", element);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
language = getLanguageFromFileExtension(scriptLocation, parserContext, element);
|
||||
if (language == null) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Unable to determine language for script '" + scriptLocation + "'", element);
|
||||
return;
|
||||
}
|
||||
language = ScriptExecutorFactory.deriveLanguageFromFileExtension(scriptLocation);
|
||||
}
|
||||
}
|
||||
|
||||
builder.addConstructorArgValue(ScriptExecutorFactory.getScriptExecutor(language));
|
||||
}
|
||||
|
||||
private String getLanguageFromFileExtension(String scriptLocation, ParserContext parserContext, Element element) {
|
||||
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||
ScriptEngine engine = null;
|
||||
|
||||
int index = scriptLocation.lastIndexOf(".") + 1;
|
||||
if (index < 1) {
|
||||
return null;
|
||||
}
|
||||
String extension = scriptLocation.substring(index);
|
||||
|
||||
engine = engineManager.getEngineByExtension(extension);
|
||||
|
||||
if (engine == null) {
|
||||
parserContext.getReaderContext().error(
|
||||
"No suitable scripting engine found for extension '" + extension + "'", element);
|
||||
}
|
||||
|
||||
return engine.getFactory().getLanguageName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.integration.scripting.dsl;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -32,6 +31,7 @@ import org.springframework.integration.scripting.jsr223.ScriptExecutingMessagePr
|
||||
import org.springframework.integration.scripting.jsr223.ScriptExecutorFactory;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -99,21 +99,16 @@ class DslScriptExecutingMessageProcessor
|
||||
this.script = this.applicationContext.getResource(this.location);
|
||||
}
|
||||
|
||||
ScriptSource scriptSource = new RefreshableResourceScriptSource(this.script, this.refreshCheckDelay);
|
||||
|
||||
if (!StringUtils.hasText(this.lang)) {
|
||||
String filename = this.script.getFilename();
|
||||
int index =
|
||||
filename != null
|
||||
? filename.lastIndexOf('.') + 1
|
||||
: -1;
|
||||
if (index < 1) {
|
||||
throw new BeanCreationException(
|
||||
"'lang' isn't provided and there is no 'file extension' for script resource: " + this.script);
|
||||
}
|
||||
this.lang = filename.substring(index);
|
||||
String scriptFilename = this.script.getFilename();
|
||||
Assert.hasText(scriptFilename,
|
||||
() -> "Either 'lang' or file extension must be provided for script: " + this.script);
|
||||
this.lang = ScriptExecutorFactory.deriveLanguageFromFileExtension(scriptFilename);
|
||||
}
|
||||
|
||||
ScriptSource scriptSource = new RefreshableResourceScriptSource(this.script, this.refreshCheckDelay);
|
||||
|
||||
if (this.applicationContext.containsBean(ScriptExecutingProcessorFactory.BEAN_NAME)) {
|
||||
ScriptExecutingProcessorFactory processorFactory =
|
||||
this.applicationContext.getBean(ScriptExecutingProcessorFactory.BEAN_NAME,
|
||||
|
||||
@@ -29,42 +29,49 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.integration.scripting.ScriptExecutor;
|
||||
import org.springframework.integration.scripting.ScriptingException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base Class for {@link ScriptExecutor}
|
||||
* Base Class for {@link ScriptExecutor}.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public abstract class AbstractScriptExecutor implements ScriptExecutor {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR - final
|
||||
|
||||
protected final ScriptEngine scriptEngine;
|
||||
|
||||
protected final String language;
|
||||
private final ScriptEngine scriptEngine;
|
||||
|
||||
protected AbstractScriptExecutor(String language) {
|
||||
Assert.hasText(language, "language must not be empty");
|
||||
this.language = language;
|
||||
|
||||
this.scriptEngine = new ScriptEngineManager().getEngineByName(this.language);
|
||||
Assert.notNull(this.scriptEngine, invalidLanguageMessage(this.language));
|
||||
this.scriptEngine = new ScriptEngineManager().getEngineByName(language);
|
||||
Assert.notNull(this.scriptEngine, () -> invalidLanguageMessage(language));
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Using script engine : " + this.scriptEngine.getFactory().getEngineName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeScript(ScriptSource scriptSource, Map<String, Object> variables) {
|
||||
Object result;
|
||||
protected AbstractScriptExecutor(ScriptEngine scriptEngine) {
|
||||
Assert.notNull(scriptEngine, "'scriptEngine' must not be null.");
|
||||
this.scriptEngine = scriptEngine;
|
||||
}
|
||||
|
||||
public ScriptEngine getScriptEngine() {
|
||||
return this.scriptEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object executeScript(ScriptSource scriptSource, Map<String, Object> variables) {
|
||||
try {
|
||||
Object result;
|
||||
String script = scriptSource.getScriptAsString();
|
||||
Date start = new Date();
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
@@ -85,13 +92,11 @@ public abstract class AbstractScriptExecutor implements ScriptExecutor {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("script executed in " + (new Date().getTime() - start.getTime()) + " ms");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
catch (Exception e) {
|
||||
throw new ScriptingException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,12 +20,16 @@ import javax.script.Bindings;
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
/**
|
||||
* Default implementation of the
|
||||
* {@link org.springframework.integration.scripting.ScriptExecutor}
|
||||
* Default implementation of the {@link AbstractScriptExecutor}.
|
||||
* Accepts a scripting language for resolving a target {@code ScriptEngine} for
|
||||
* evaluation and does nothing with the {@code result} in the
|
||||
* {@link #postProcess(Object, ScriptEngine, String, Bindings)} implementation.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class DefaultScriptExecutor extends AbstractScriptExecutor {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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.Bindings;
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
import org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory;
|
||||
|
||||
|
||||
/**
|
||||
* An {@link AbstractScriptExecutor} for the Kotlin scripts support.
|
||||
* Uses {@link KotlinJsr223JvmLocalScriptEngineFactory} directly since there is
|
||||
* no {@code META-INF/services/javax.script.ScriptEngineFactory} file in CLASSPATH.
|
||||
* Also sets an {@code idea.use.native.fs.for.win} system property to {@code false}
|
||||
* to disable a native engine discovery for Windows: bay be resolved in the future Kotlin versions.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
public class KotlinScriptExecutor extends AbstractScriptExecutor {
|
||||
|
||||
static {
|
||||
System.setProperty("idea.use.native.fs.for.win", "false");
|
||||
}
|
||||
|
||||
public KotlinScriptExecutor() {
|
||||
super(new KotlinJsr223JvmLocalScriptEngineFactory().getScriptEngine());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object postProcess(Object result, ScriptEngine scriptEngine, String script, Bindings bindings) {
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,24 +16,28 @@
|
||||
|
||||
package org.springframework.integration.scripting.jsr223;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link DefaultScriptExecutor} extension for Ruby scripting support.
|
||||
* It is present here only for the reason to populate
|
||||
* {@code org.jruby.embed.localvariable.behavior} and
|
||||
* {@code org.jruby.embed.localcontext.scope} system properties.
|
||||
* May be revised in the future.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.1
|
||||
*
|
||||
*/
|
||||
public 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");
|
||||
}
|
||||
System.setProperty("org.jruby.embed.localvariable.behavior", "transient");
|
||||
System.setProperty("org.jruby.embed.localcontext.scope", "threadsafe");
|
||||
}
|
||||
|
||||
public RubyScriptExecutor() {
|
||||
super("ruby");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AbstractScriptExecutingMessageProcessor} implementation for evaluating scripts
|
||||
* from the provided {@link ScriptSource} in the provided {@link ScriptExecutor} against an optional
|
||||
* binding {@code variables}.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*
|
||||
@@ -36,7 +40,7 @@ public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMess
|
||||
|
||||
private final ScriptExecutor scriptExecutor;
|
||||
|
||||
private volatile ScriptSource scriptSource;
|
||||
private final ScriptSource scriptSource;
|
||||
|
||||
|
||||
/**
|
||||
@@ -46,8 +50,7 @@ public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMess
|
||||
* @param scriptExecutor The script executor.
|
||||
*/
|
||||
public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptExecutor scriptExecutor) {
|
||||
this.scriptSource = scriptSource;
|
||||
this.scriptExecutor = scriptExecutor;
|
||||
this(scriptSource, new DefaultScriptVariableGenerator(), scriptExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +64,8 @@ public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMess
|
||||
ScriptExecutor scriptExecutor) {
|
||||
|
||||
super(scriptVariableGenerator);
|
||||
Assert.notNull(scriptSource, "'scriptSource' must not be null");
|
||||
Assert.notNull(scriptExecutor, "'scriptExecutor' must not be null");
|
||||
this.scriptSource = scriptSource;
|
||||
this.scriptExecutor = scriptExecutor;
|
||||
}
|
||||
@@ -75,9 +80,7 @@ public class ScriptExecutingMessageProcessor extends AbstractScriptExecutingMess
|
||||
public ScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptExecutor scriptExecutor,
|
||||
Map<String, Object> variables) {
|
||||
|
||||
super(new DefaultScriptVariableGenerator(variables));
|
||||
this.scriptSource = scriptSource;
|
||||
this.scriptExecutor = scriptExecutor;
|
||||
this(scriptSource, new DefaultScriptVariableGenerator(variables), scriptExecutor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,13 +16,21 @@
|
||||
|
||||
package org.springframework.integration.scripting.jsr223;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import org.springframework.integration.scripting.ScriptExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The scripting configuration utilities.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public abstract class ScriptExecutorFactory {
|
||||
public final class ScriptExecutorFactory {
|
||||
|
||||
public static ScriptExecutor getScriptExecutor(String language) {
|
||||
if (language.equalsIgnoreCase("python") || language.equalsIgnoreCase("jython")) {
|
||||
@@ -31,7 +39,33 @@ public abstract class ScriptExecutorFactory {
|
||||
else if (language.equalsIgnoreCase("ruby") || language.equalsIgnoreCase("jruby")) {
|
||||
return new RubyScriptExecutor();
|
||||
}
|
||||
else if (language.equalsIgnoreCase("kotlin")) {
|
||||
return new KotlinScriptExecutor();
|
||||
}
|
||||
return new DefaultScriptExecutor(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a scripting language from the provided script file name.
|
||||
* @param scriptLocation the script file to consult for extension.
|
||||
* @return the language name for the {@link ScriptExecutor}.
|
||||
* @since 5.2
|
||||
*/
|
||||
public static String deriveLanguageFromFileExtension(String scriptLocation) {
|
||||
int index = scriptLocation.lastIndexOf(".") + 1;
|
||||
Assert.state(index > 0, () -> "Unable to determine language for script '" + scriptLocation + "'");
|
||||
String extension = scriptLocation.substring(index);
|
||||
if (extension.equals("kts")) {
|
||||
return "kotlin";
|
||||
}
|
||||
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||
ScriptEngine engine = engineManager.getEngineByExtension(extension);
|
||||
Assert.state(engine != null, () -> "No suitable scripting engine found for extension '" + extension + "'");
|
||||
return engine.getFactory().getLanguageName();
|
||||
}
|
||||
|
||||
private ScriptExecutorFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.channel.QueueChannelOperations;
|
||||
@@ -100,7 +101,7 @@ public class ScriptsTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws IOException {
|
||||
SCRIPT_FILE = FOLDER.newFile("script.jython");
|
||||
SCRIPT_FILE = FOLDER.newFile("script.py");
|
||||
FileCopyUtils.copy("1".getBytes(), SCRIPT_FILE);
|
||||
}
|
||||
|
||||
@@ -152,7 +153,7 @@ public class ScriptsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void routerTest() throws IOException {
|
||||
public void routerTest() {
|
||||
this.scriptRouterInput.send(new GenericMessage<>("aardvark"));
|
||||
this.scriptRouterInput.send(new GenericMessage<>("bear"));
|
||||
this.scriptRouterInput.send(new GenericMessage<>("cat"));
|
||||
@@ -166,7 +167,7 @@ public class ScriptsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageSourceTest() throws IOException, InterruptedException {
|
||||
public void messageSourceTest() throws InterruptedException {
|
||||
Message<?> message = this.messageSourceChannel.receive(20000);
|
||||
assertThat(message).isNotNull();
|
||||
Object payload = message.getPayload();
|
||||
@@ -180,6 +181,20 @@ public class ScriptsTests {
|
||||
assertThat(this.messageSourceChannel.receive(20000)).isNotNull();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Qualifier("kotlinScriptFlow.input")
|
||||
private MessageChannel kotlinScriptFlowInput;
|
||||
|
||||
@Test
|
||||
public void testKotlinScript() {
|
||||
this.kotlinScriptFlowInput.send(new GenericMessage<>(3));
|
||||
Message<?> receive = this.results.receive(10_000);
|
||||
assertThat(receive).isNotNull()
|
||||
.extracting(Message::getPayload)
|
||||
.isEqualTo(5);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public static class ContextConfiguration {
|
||||
@@ -187,7 +202,7 @@ public class ScriptsTests {
|
||||
@Value("scripts/TesSplitterScript.groovy")
|
||||
private Resource splitterScript;
|
||||
|
||||
@Value("scripts/TestFilterScript.groovy")
|
||||
@Value("scripts/TestFilterScript.kts")
|
||||
private Resource filterScript;
|
||||
|
||||
@Bean
|
||||
@@ -244,12 +259,21 @@ public class ScriptsTests {
|
||||
@Bean
|
||||
public IntegrationFlow scriptPollingAdapter() {
|
||||
return IntegrationFlows
|
||||
.from(Scripts.messageSource("scripts/TestMessageSourceScript.ruby"),
|
||||
.from(Scripts.messageSource("scripts/TestMessageSourceScript.rb"),
|
||||
e -> e.poller(p -> p.fixedDelay(100)))
|
||||
.channel(c -> c.queue("messageSourceChannel"))
|
||||
.get();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow kotlinScriptFlow() {
|
||||
return f -> f
|
||||
.handle(Scripts.processor(new ByteArrayResource("2 + bindings[\"payload\"] as Int".getBytes()))
|
||||
.lang("kotlin"))
|
||||
.channel(results());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
<int-script:script location="foo.groovy"/>
|
||||
<int-script:script location="foo.js"/>
|
||||
<int-script:script location="foo.py"/>
|
||||
<int-script:script location="foo.kts"/>
|
||||
</beans>
|
||||
|
||||
@@ -17,76 +17,74 @@
|
||||
package org.springframework.integration.scripting.jsr223;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringRunner.class)
|
||||
public class DeriveLanguageFromExtensionTests {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext ctx;
|
||||
|
||||
@Test
|
||||
public void testParseLanguage() {
|
||||
String[] langs = { "ruby", "Groovy", "ECMAScript", "python" };
|
||||
String[] langs = { "ruby", "Groovy", "ECMAScript", "python", "kotlin" };
|
||||
Class<?>[] executors = {
|
||||
RubyScriptExecutor.class,
|
||||
DefaultScriptExecutor.class,
|
||||
DefaultScriptExecutor.class,
|
||||
PythonScriptExecutor.class
|
||||
PythonScriptExecutor.class,
|
||||
KotlinScriptExecutor.class
|
||||
};
|
||||
|
||||
Map<String, ScriptExecutingMessageProcessor> scriptProcessors = ctx
|
||||
.getBeansOfType(ScriptExecutingMessageProcessor.class);
|
||||
assertThat(scriptProcessors.size()).isEqualTo(4);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Map<String, ScriptExecutingMessageProcessor> scriptProcessors =
|
||||
this.ctx.getBeansOfType(ScriptExecutingMessageProcessor.class);
|
||||
assertThat(scriptProcessors.size()).isEqualTo(5);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ScriptExecutingMessageProcessor processor = ctx.getBean(
|
||||
"org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor#" + i,
|
||||
ScriptExecutingMessageProcessor.class);
|
||||
|
||||
AbstractScriptExecutor executor = (AbstractScriptExecutor) TestUtils.getPropertyValue(processor,
|
||||
"scriptExecutor");
|
||||
assertThat(executor.language).isEqualTo(langs[i]);
|
||||
AbstractScriptExecutor executor =
|
||||
TestUtils.getPropertyValue(processor, "scriptExecutor", AbstractScriptExecutor.class);
|
||||
assertThat(executor.getScriptEngine().getFactory().getLanguageName()).isEqualTo(langs[i]);
|
||||
assertThat(executor.getClass()).isEqualTo(executors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadExtension() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail1-context.xml", this.getClass())
|
||||
.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e.getMessage().contains("No suitable scripting engine found for extension 'xx'")).isTrue();
|
||||
}
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-fail1-context.xml",
|
||||
getClass()).close())
|
||||
.withMessageContaining("No suitable scripting engine found for extension 'xx'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoExtension() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail2-context.xml", this.getClass())
|
||||
.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e.getMessage().contains("Unable to determine language for script 'foo'")).isTrue();
|
||||
}
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-fail2-context.xml",
|
||||
getClass()).close())
|
||||
.withMessageContaining("Unable to determine language for script 'foo'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.scripting.jsr223;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -41,9 +42,9 @@ public class Jsr223ScriptExecutorTests {
|
||||
executor.executeScript(new StaticScriptSource("'hello, world'"));
|
||||
executor.executeScript(new StaticScriptSource("'hello, again'"));
|
||||
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
|
||||
Map<String, Object> headers = new HashMap<String, Object>();
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("one", 1);
|
||||
headers.put("two", "two");
|
||||
headers.put("three", 3);
|
||||
@@ -54,14 +55,14 @@ public class Jsr223ScriptExecutorTests {
|
||||
Resource resource = new ClassPathResource("/org/springframework/integration/scripting/jsr223/print_message.rb");
|
||||
String result = (String) executor.executeScript(new ResourceScriptSource(resource), variables);
|
||||
|
||||
assertThat(result.substring(0, "payload modified".length())).isEqualTo("payload modified");
|
||||
assertThat(result).isNotNull().contains("payload modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJs() {
|
||||
ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("js");
|
||||
Object obj = executor.executeScript(new StaticScriptSource("function js(){ return 'js';} js();"));
|
||||
assertThat(obj.toString()).isEqualTo("js");
|
||||
assertThat(obj).isNotNull().isEqualTo("js");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -74,9 +75,9 @@ public class Jsr223ScriptExecutorTests {
|
||||
assertThat(obj).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void testInvalidLanguageThrowsIllegalArgumentException() {
|
||||
ScriptExecutorFactory.getScriptExecutor("foo");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ScriptExecutorFactory.getScriptExecutor("foo"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,32 +39,32 @@ import org.springframework.scripting.support.StaticScriptSource;
|
||||
*/
|
||||
public class PythonScriptExecutorTests {
|
||||
|
||||
ScriptExecutor executor;
|
||||
private ScriptExecutor executor;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
executor = new PythonScriptExecutor();
|
||||
this.executor = new PythonScriptExecutor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiteral() {
|
||||
Object obj = executor.executeScript(new StaticScriptSource("3+4"));
|
||||
Object obj = this.executor.executeScript(new StaticScriptSource("3+4"));
|
||||
assertThat(obj).isEqualTo(7);
|
||||
|
||||
obj = executor.executeScript(new StaticScriptSource("'hello,world'"));
|
||||
obj = this.executor.executeScript(new StaticScriptSource("'hello,world'"));
|
||||
assertThat(obj).isEqualTo("hello,world");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
public void test1() {
|
||||
Object obj = executor.executeScript(new StaticScriptSource("x=2"));
|
||||
Object obj = this.executor.executeScript(new StaticScriptSource("x=2"));
|
||||
assertThat(obj).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
Object obj = executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)"));
|
||||
Object obj = this.executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)"));
|
||||
assertThat(obj).isEqualTo(2);
|
||||
}
|
||||
|
||||
@@ -73,9 +73,13 @@ public class PythonScriptExecutorTests {
|
||||
ScriptSource source =
|
||||
new ResourceScriptSource(
|
||||
new ClassPathResource("/org/springframework/integration/scripting/jsr223/test3.py"));
|
||||
Object obj = executor.executeScript(source);
|
||||
PyTuple tuple = (PyTuple) obj;
|
||||
assertThat(tuple.get(0)).isEqualTo(1);
|
||||
Object obj = this.executor.executeScript(source);
|
||||
assertThat(obj)
|
||||
.isNotNull()
|
||||
.isInstanceOf(PyTuple.class)
|
||||
.asList()
|
||||
.element(0)
|
||||
.isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -85,17 +89,20 @@ public class PythonScriptExecutorTests {
|
||||
new ClassPathResource("/org/springframework/integration/scripting/jsr223/test3.py"));
|
||||
HashMap<String, Object> variables = new HashMap<>();
|
||||
variables.put("foo", "bar");
|
||||
Object obj = executor.executeScript(source, variables);
|
||||
assertThat(obj).isNotNull();
|
||||
PyTuple tuple = (PyTuple) obj;
|
||||
assertThat(tuple.get(0)).isEqualTo(1);
|
||||
Object obj = this.executor.executeScript(source, variables);
|
||||
assertThat(obj)
|
||||
.isNotNull()
|
||||
.isInstanceOf(PyTuple.class)
|
||||
.asList()
|
||||
.element(0)
|
||||
.isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmbeddedVariable() {
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put("scope", "world");
|
||||
Object obj = executor.executeScript(new StaticScriptSource("\"hello, %s\"% scope"), variables);
|
||||
Object obj = this.executor.executeScript(new StaticScriptSource("\"hello, %s\"% scope"), variables);
|
||||
assertThat(obj).isEqualTo("hello, world");
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
headers.type == 'good'
|
||||
@@ -0,0 +1 @@
|
||||
(bindings["headers"] as Map<String, *>)["type"] == "good"
|
||||
Reference in New Issue
Block a user