Refactor scripting module

Since Nashorn JavaScript Engine is deprecated in Java 11 (https://openjdk.java.net/jeps/335)
it is better do nothing with JavaScript in the project any more

* Replace JS script tests to some other languages
* Mentioned deprecation in the Docs
* Rework `scripting.adoc` for code snippet switches
This commit is contained in:
Artem Bilan
2021-03-01 13:38:03 -05:00
parent df3fc4c9a6
commit f285e81d08
15 changed files with 162 additions and 100 deletions

View File

@@ -16,26 +16,22 @@
</channel>
<router input-channel="referencedScriptInput">
<int-script:script lang="javascript" location="org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.js">
<int-script:script lang="groovy" location="org/springframework/integration/scripting/config/jsr223/Jsr223RouterTests.groovy">
<variable name="maxLen" value="3"/>
</int-script:script>
</router>
<router input-channel="inlineScriptInput">
<int-script:script lang="javascript"><![CDATA[
(function(){
return payload.length > 5 ? "longStrings" : "shortStrings";
})();
]]></int-script:script>
<int-script:script lang="groovy">
payload.length() > 5 ? 'longStrings' : 'shortStrings'
</int-script:script>
</router>
<chain input-channel="scriptRouterWithinChainInput">
<router>
<int-script:script lang="javascript"><![CDATA[
(function(){
return payload.length > 5 ? "longStrings" : "shortStrings";
})();
]]></int-script:script>
<int-script:script lang="groovy">
payload.length() > 5 ? 'longStrings' : 'shortStrings'
</int-script:script>
</router>
</chain>

View File

@@ -0,0 +1 @@
payload.length() > maxLen as int ? 'longStrings' : 'shortStrings'

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -18,25 +18,27 @@ package org.springframework.integration.scripting.config.jsr223;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Mark Fisher
* @author David Turanski
* @author Artem Bilan
*
* @since 2.1
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig
@DirtiesContext
public class Jsr223RouterTests {
@Autowired
@@ -49,19 +51,25 @@ public class Jsr223RouterTests {
private MessageChannel scriptRouterWithinChainInput;
@Autowired
private PollableChannel longStrings;
private QueueChannel longStrings;
@Autowired
private PollableChannel shortStrings;
private QueueChannel shortStrings;
@AfterEach
void cleanUp() {
this.longStrings.clear();
this.shortStrings.clear();
}
@Test
public void referencedScript() { // long is > 3
Message<?> message1 = new GenericMessage<String>("aardvark");
Message<?> message2 = new GenericMessage<String>("bear");
Message<?> message3 = new GenericMessage<String>("cat");
Message<?> message4 = new GenericMessage<String>("dog");
Message<?> message5 = new GenericMessage<String>("elephant");
Message<?> message1 = new GenericMessage<>("aardvark");
Message<?> message2 = new GenericMessage<>("bear");
Message<?> message3 = new GenericMessage<>("cat");
Message<?> message4 = new GenericMessage<>("dog");
Message<?> message5 = new GenericMessage<>("elephant");
this.referencedScriptInput.send(message1);
this.referencedScriptInput.send(message2);
this.referencedScriptInput.send(message3);
@@ -78,11 +86,11 @@ public class Jsr223RouterTests {
@Test
public void inlineScript() { // long is > 5
Message<?> message1 = new GenericMessage<String>("aardvark");
Message<?> message2 = new GenericMessage<String>("bear");
Message<?> message3 = new GenericMessage<String>("cat");
Message<?> message4 = new GenericMessage<String>("dog");
Message<?> message5 = new GenericMessage<String>("elephant");
Message<?> message1 = new GenericMessage<>("aardvark");
Message<?> message2 = new GenericMessage<>("bear");
Message<?> message3 = new GenericMessage<>("cat");
Message<?> message4 = new GenericMessage<>("dog");
Message<?> message5 = new GenericMessage<>("elephant");
this.inlineScriptInput.send(message1);
this.inlineScriptInput.send(message2);
this.inlineScriptInput.send(message3);
@@ -99,11 +107,11 @@ public class Jsr223RouterTests {
@Test
public void testInt2893ScriptRouterWithinChain() {
Message<?> message1 = new GenericMessage<String>("aardvark");
Message<?> message2 = new GenericMessage<String>("bear");
Message<?> message3 = new GenericMessage<String>("cat");
Message<?> message4 = new GenericMessage<String>("dog");
Message<?> message5 = new GenericMessage<String>("elephant");
Message<?> message1 = new GenericMessage<>("aardvark");
Message<?> message2 = new GenericMessage<>("bear");
Message<?> message3 = new GenericMessage<>("cat");
Message<?> message4 = new GenericMessage<>("dog");
Message<?> message5 = new GenericMessage<>("elephant");
this.scriptRouterWithinChainInput.send(message1);
this.scriptRouterWithinChainInput.send(message2);
this.scriptRouterWithinChainInput.send(message3);

View File

@@ -1,4 +0,0 @@
function route(max) {
return payload.length > max ? "longStrings" : "shortStrings";
}
route(maxLen);

View File

@@ -29,7 +29,7 @@
<beans:bean id="scriptVarSource"
class="org.springframework.integration.scripting.config.jsr223.Jsr223ServiceActivatorTests$SampleScriptVariSource"/>
class="org.springframework.integration.scripting.config.jsr223.Jsr223ServiceActivatorTests$SampleScriptVariableSource"/>
<service-activator input-channel="inlineScriptInput">
<script:script lang="ruby" variables="foo=#{'FOO'}, date-ref=date">

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -160,7 +160,7 @@ public class Jsr223ServiceActivatorTests {
.withMessageContaining("Duplicated variable: foo");
}
public static class SampleScriptVariSource implements ScriptVariableGenerator {
public static class SampleScriptVariableSource implements ScriptVariableGenerator {
@Override
public Map<String, Object> generateScriptVariables(Message<?> message) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2019 the original author or authors.
* Copyright 2016-2021 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.
@@ -22,12 +22,10 @@ import java.io.File;
import java.io.IOException;
import java.util.Date;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -47,6 +45,7 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.FileCopyUtils;
@@ -55,12 +54,12 @@ import org.springframework.util.FileCopyUtils;
*
* @since 5.0
*/
@RunWith(SpringRunner.class)
@SpringJUnitConfig
@DirtiesContext
public class ScriptsTests {
@ClassRule
public static final TemporaryFolder FOLDER = new TemporaryFolder();
@TempDir
public static File FOLDER;
private static File SCRIPT_FILE;
@@ -99,13 +98,13 @@ public class ScriptsTests {
@Autowired
private PollableChannel messageSourceChannel;
@BeforeClass
@BeforeAll
public static void setup() throws IOException {
SCRIPT_FILE = FOLDER.newFile("script.py");
SCRIPT_FILE = new File(FOLDER, "script.py");
FileCopyUtils.copy("1".getBytes(), SCRIPT_FILE);
}
@After
@AfterEach
public void clear() {
((QueueChannelOperations) this.results).clear();
}
@@ -243,7 +242,7 @@ public class ScriptsTests {
@Bean
public IntegrationFlow scriptRouter() {
return f -> f.route(Scripts.processor("scripts/TestRouterScript.js"));
return f -> f.route(Scripts.processor("scripts/TestRouterScript.py"));
}
@Bean

View File

@@ -7,7 +7,6 @@
<int-script:script location="foo.rb"/>
<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>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -21,22 +21,21 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
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.junit4.SpringRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author David Turanski
* @author Artem Bilan
*
*/
@RunWith(SpringRunner.class)
@SpringJUnitConfig
public class DeriveLanguageFromExtensionTests {
@Autowired
@@ -44,20 +43,19 @@ public class DeriveLanguageFromExtensionTests {
@Test
public void testParseLanguage() {
String[] langs = { "ruby", "Groovy", "ECMAScript", "python", "kotlin" };
String[] langs = { "ruby", "Groovy", "python", "kotlin" };
Class<?>[] executors = {
RubyScriptExecutor.class,
DefaultScriptExecutor.class,
DefaultScriptExecutor.class,
PythonScriptExecutor.class,
KotlinScriptExecutor.class
};
Map<String, ScriptExecutingMessageProcessor> scriptProcessors =
this.ctx.getBeansOfType(ScriptExecutingMessageProcessor.class);
assertThat(scriptProcessors.size()).isEqualTo(5);
assertThat(scriptProcessors.size()).isEqualTo(4);
for (int i = 0; i < 5; i++) {
for (int i = 0; i < 4; i++) {
ScriptExecutingMessageProcessor processor = ctx.getBean(
"org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor#" + i,
ScriptExecutingMessageProcessor.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -22,7 +22,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -58,13 +58,6 @@ public class Jsr223ScriptExecutorTests {
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).isNotNull().isEqualTo("js");
}
@Test
public void testPython() {
ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("python");

View File

@@ -1,7 +1,6 @@
require 'java'
class RubyHello
include_class 'com.dturanski.test.jruby.Hello'
def say
"hello,world"
end

View File

@@ -1,5 +1,5 @@
require "java"
include_class 'java.util.Date'
java_import 'java.util.Date'
#payload and headers a global variable
if payload
payload = payload+" modified #{Date.new}"

View File

@@ -1,3 +0,0 @@
(function(){
return payload.length > 5 ? "longStrings" : "shortStrings";
})();

View File

@@ -0,0 +1 @@
'longStrings' if len(payload) > 5 else 'shortStrings'

View File

@@ -2,14 +2,17 @@
=== Scripting Support
Spring Integration 2.1 added support for the https://www.jcp.org/en/jsr/detail?id=223[JSR223 Scripting for Java specification], introduced in Java version 6.
It lets you use scripts written in any supported language (including Ruby, JRuby, Javascript, Groovy and Kotlin) to provide the logic for various integration components, similar to the way the Spring Expression Language (SpEL) is used in Spring Integration.
It lets you use scripts written in any supported language (including Ruby, JRuby, Groovy and Kotlin) to provide the logic for various integration components, similar to the way the Spring Expression Language (SpEL) is used in Spring Integration.
For more information about JSR223, see the https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/api.html[documentation].
NOTE: Starting with Java 11, the Nashorn JavaScript Engine has been deprecated with possible removal in Java 15.
It is recommended to reconsider in favor of other scripting language from now on.
You need to include this dependency into your project:
====
[source, xml, subs="normal", role="primary"]
.Maven
[source, xml, subs="normal"]
----
<dependency>
<groupId>org.springframework.integration</groupId>
@@ -17,9 +20,8 @@ You need to include this dependency into your project:
<version>{project-version}</version>
</dependency>
----
[source, groovy, subs="normal", role="secondary"]
.Gradle
[source, groovy, subs="normal"]
----
compile "org.springframework.integration:spring-integration-scripting:{project-version}"
----
@@ -30,8 +32,29 @@ In addition you need to add a script engine implementation, e.g. JRuby, Jython.
Starting with version 5.2, Spring Integration provides a Kotlin Jsr223 support.
You need to add these dependencies into your project to make it working:
====
[source, groovy]
[source, xml, subs="normal", role="primary"]
.Maven
----
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-script-util</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-compiler-embeddable</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-compiler-embeddable</artifactId>
<scope>runtime</scope>
</dependency>
----
[source, groovy, subs="normal", role="secondary"]
.Gradle
----
runtime 'org.jetbrains.kotlin:kotlin-script-util'
runtime 'org.jetbrains.kotlin:kotlin-compiler-embeddable'
@@ -41,10 +64,7 @@ runtime 'org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable'
The `KotlinScriptExecutor` is selected by the provided `kotlin` language indicator or script file comes with the `.kts` extension.
IMPORTANT: Note that this feature requires Java 6 or higher.
In order to use a JVM scripting language, a JSR223 implementation for that language must be included in your class path.
Java 6 natively supports Javascript.
The https://groovy-lang.org/[Groovy] and https://www.jruby.org[JRuby] projects provide JSR233 support in their standard distributions.
IMPORTANT: Various JSR223 language implementations have been developed by third parties.
@@ -61,9 +81,27 @@ To enable scripting support, Spring Integration defines a `ScriptExecutingMessag
All you need to do is write a script that uses these variables.
The following pair of examples show sample configurations that create filters:
.Filter
====
[source,xml]
[source, java, role="primary"]
.Java DSL
----
@Bean
public IntegrationFlow scriptFilter() {
return f -> f.filter(Scripts.processor("some/path/to/ruby/script/RubyFilterTests.rb"));
}
...
@Bean
public Resource scriptResource() {
return new ByteArrayResource("headers.type == 'good'".getBytes());
}
@Bean
public IntegrationFlow scriptFilter() {
return f -> f.filter(Scripts.processor(scriptResource()).lang("groovy"));
}
----
[source, xml, role="secondary"]
.XML
----
<int:filter input-channel="referencedScriptInput">
<int-script:script location="some/path/to/ruby/script/RubyFilterTests.rb"/>
@@ -80,7 +118,7 @@ The following pair of examples show sample configurations that create filters:
====
As the preceding examples show, the script can be included inline or can be included by reference to a resource location (by using the `location` attribute).
Additionally, the `lang` attribute corresponds to the language name (or its JSR223 alias)
Additionally, the `lang` attribute corresponds to the language name (or its JSR223 alias).
Other Spring Integration endpoint elements that support scripting include `router`, `service-activator`, `transformer`, and `splitter`.
The scripting configuration in each case would be identical to the above (besides the endpoint element).
@@ -89,7 +127,14 @@ Another useful feature of scripting support is the ability to update (reload) sc
To do so, specify the `refresh-check-delay` attribute on the `script` element, as the following example shows:
====
[source,xml]
[source, java, role="primary"]
.Java DSL
----
Scripts.processor(...).refreshCheckDelay(5000)
}
----
[source, xml, role="secondary"]
.XML
----
<int-script:script location="..." refresh-check-delay="5000"/>
----
@@ -101,7 +146,14 @@ If the script is updated, any invocation that occurs later than 5 seconds since
Consider the following example:
====
[source,xml]
[source, java, role="primary"]
.Java DSL
----
Scripts.processor(...).refreshCheckDelay(0)
}
----
[source, xml, role="secondary"]
.XML
----
<int-script:script location="..." refresh-check-delay="0"/>
----
@@ -113,7 +165,14 @@ This is the default behavior.
The following example shows a script that never updates:
====
[source,xml]
[source, java, role="primary"]
.Java DSL
----
Scripts.processor(...).refreshCheckDelay(-1)
}
----
[source, xml, role="secondary"]
.XML
----
<int-script:script location="..." refresh-check-delay="-1"/>
----
@@ -126,14 +185,22 @@ IMPORTANT: Inline scripts can not be reloaded.
Variable bindings are required to enable the script to reference variables externally provided to the script's execution context.
By default, `payload` and `headers` are used as binding variables.
You can bind additional variables to a script by using `<variable>` elements, as the following example shows:
You can bind additional variables to a script by using `<variable>` elements (or `ScriptSpec.variables()` option), as the following example shows:
====
[source,xml]
[source, java, role="primary"]
.Java DSL
----
<script:script lang="js" location="foo/bar/MyScript.js">
<script:variable name="foo" value="thing1"/>
<script:variable name="bar" value="thing2"/>
Scripts.processor("foo/bar/MyScript.py")
.variables(Map.of("var1", "thing1", "var2", "thing2", "date", date))
}
----
[source, xml, role="secondary"]
.XML
----
<script:script lang="py" location="foo/bar/MyScript.py">
<script:variable name="var1" value="thing1"/>
<script:variable name="var2" value="thing2"/>
<script:variable name="date" ref="date"/>
</script:script>
----
@@ -191,7 +258,15 @@ This method is called every time the script is executed for a message.
The following example shows how to provide an implementation of `ScriptVariableGenerator` and reference it with the `script-variable-generator` attribute:
====
[source,xml]
[source, java, role="primary"]
.Java DSL
----
Scripts.processor("foo/bar/MyScript.groovy")
.variableGenerator(new foo.bar.MyScriptVariableGenerator())
}
----
[source, xml, role="secondary"]
.XML
----
<int-script:script location="foo/bar/MyScript.groovy"
script-variable-generator="variableGenerator"/>