From 6641b1f5455f487155ac036b56c5e4de5d436368 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 11 Oct 2022 14:38:46 -0400 Subject: [PATCH] GH-3664: Re-enable JavaScript support via GraalVM (#3911) * GH-3664: Re-enable JavaScript support via GraalVM Fixes https://github.com/spring-projects/spring-integration/issues/3664 The JavaScript JSR223 engine has been removed from Java. Migrate JavaScript support into GraalVM Polyglot API: * Implement `PolyglotScriptExecutor` which can also support other GraalVM languages * Change the `ScriptExecutorFactory` to use a `PolyglotScriptExecutor` when it encounters JavaScript * Re-enable tests for JavaScript * Add GraalVM Polyglot support into docs * * Reinstate `@EnabledIfSystemProperty` for GraalVM JS system property --- build.gradle | 3 +- .../scripting/PolyglotScriptExecutor.java | 77 +++++++++++++++++++ .../jsr223/ScriptExecutorFactory.java | 9 ++- .../jsr223/Jsr223HeaderEnricherTests.java | 2 +- .../Jsr223InboundChannelAdapterTests.java | 2 +- .../config/jsr223/Jsr223TransformerTests.java | 2 +- src/reference/asciidoc/scripting.adoc | 25 +++++- src/reference/asciidoc/whats-new.adoc | 7 ++ 8 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 spring-integration-scripting/src/main/java/org/springframework/integration/scripting/PolyglotScriptExecutor.java diff --git a/build.gradle b/build.gradle index 00d5173eab..f946e2e2b1 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,7 @@ ext { derbyVersion = '10.14.2.0' findbugsVersion = '3.0.1' ftpServerVersion = '1.2.0' + graalvmVersion = '22.2.0' greenmailVersion = '2.0.0-alpha-2' groovyVersion = '3.0.13' hamcrestVersion = '2.2' @@ -848,9 +849,9 @@ project('spring-integration-scripting') { exclude group: 'org.jetbrains.kotlin', module: 'kotlin-daemon-client' } optionalApi 'org.jetbrains.kotlin:kotlin-compiler-embeddable' + providedImplementation "org.graalvm.sdk:graal-sdk:$graalvmVersion" testImplementation "org.jruby:jruby-complete:$jrubyVersion" - testImplementation "org.codehaus.groovy:groovy:$groovyVersion" testImplementation "org.codehaus.groovy:groovy-jsr223:$groovyVersion" testImplementation "org.python:jython-standalone:$jythonVersion" diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/PolyglotScriptExecutor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/PolyglotScriptExecutor.java new file mode 100644 index 0000000000..78cf41d8f0 --- /dev/null +++ b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/PolyglotScriptExecutor.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 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; + +import java.util.Map; + +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; + +import org.springframework.lang.Nullable; +import org.springframework.scripting.ScriptSource; +import org.springframework.util.Assert; + +/** + * GraalVM Polyglot {@link ScriptExecutor} implementation. + * + * @author Artem Bilan + * + * @since 6.0 + */ +public class PolyglotScriptExecutor implements ScriptExecutor { + + private final String language; + + private Context.Builder contextBuilder; + + /** + * Construct an executor based on the provided language id. + * @param language the supported by GraalVM language id. + */ + public PolyglotScriptExecutor(String language) { + this(language, Context.newBuilder().allowAllAccess(true)); + } + + /** + * Construct an executor based on the provided language id. + * @param language the supported by GraalVM language id. + */ + public PolyglotScriptExecutor(String language, Context.Builder contextBuilder) { + Assert.hasText(language, "'language' must not be empty"); + Assert.notNull(contextBuilder, "'contextBuilder' must not be null"); + this.contextBuilder = contextBuilder; + this.language = language; + try (Context context = this.contextBuilder.build()) { + context.initialize(language); + } + } + + @Override + public Object executeScript(ScriptSource scriptSource, @Nullable Map variables) { + try (Context context = this.contextBuilder.build()) { + if (variables != null) { + Value bindings = context.getBindings(this.language); + variables.forEach(bindings::putMember); + } + return context.eval(this.language, scriptSource.getScriptAsString()).as(Object.class); + } + catch (Exception ex) { + throw new ScriptingException(ex.getMessage(), ex); + } + } + +} diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutorFactory.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutorFactory.java index 479a84da6b..273bac0dcc 100644 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutorFactory.java +++ b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/jsr223/ScriptExecutorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -19,6 +19,7 @@ package org.springframework.integration.scripting.jsr223; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; +import org.springframework.integration.scripting.PolyglotScriptExecutor; import org.springframework.integration.scripting.ScriptExecutor; import org.springframework.util.Assert; @@ -42,6 +43,9 @@ public final class ScriptExecutorFactory { else if (language.equalsIgnoreCase("kotlin")) { return new KotlinScriptExecutor(); } + else if (language.equalsIgnoreCase("js") || language.equalsIgnoreCase("javascript")) { + return new PolyglotScriptExecutor("js"); + } return new DefaultScriptExecutor(language); } @@ -58,6 +62,9 @@ public final class ScriptExecutorFactory { if (extension.equals("kts")) { return "kotlin"; } + else if (extension.equals("js")) { + return "js"; + } ScriptEngineManager engineManager = new ScriptEngineManager(); ScriptEngine engine = engineManager.getEngineByExtension(extension); Assert.state(engine != null, () -> "No suitable scripting engine found for extension '" + extension + "'"); diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.java index 9fc9f5a5dc..39161bc3e4 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223HeaderEnricherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests.java index f5162ccdb6..161b2802cb 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223InboundChannelAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-2022 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. diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.java index ed585d1e44..62792f9a1b 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223TransformerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/src/reference/asciidoc/scripting.adoc b/src/reference/asciidoc/scripting.adoc index 0429d41089..c36356bc79 100644 --- a/src/reference/asciidoc/scripting.adoc +++ b/src/reference/asciidoc/scripting.adoc @@ -5,9 +5,6 @@ Spring Integration 2.1 added support for the https://www.jcp.org/en/jsr/detail?i 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 it in favor of other scripting language from now on. - You need to include this dependency into your project: ==== @@ -32,7 +29,6 @@ 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, xml, subs="normal", role="primary"] .Maven @@ -279,3 +275,24 @@ If a `script-variable-generator` is not provided, script components use `Default IMPORTANT: You cannot provide both the `script-variable-generator` attribute and `` element(s). They are mutually exclusive. + +[[graalmv-polyglot]] +===== GraalVM Polyglot + +Starting with version 6.0, the framework provides a `PolyglotScriptExecutor` which is based the https://www.graalvm.org/22.2/reference-manual/embed-languages[GraalVM Polyglot API]. +The JSR223 engine implementation for JavaScript, removed from Java by itself, has been replaced by using this new script executor. +See more information about enabling JavaScript support in GraalVM and what https://www.graalvm.org/22.2/reference-manual/js[configuration options] can be propagated via script variables. +By default, the framework sets `allowAllAccess` to `true` on the shared Polyglot `Context` which enables this interaction with host JVM: + +* The creation and use of new threads. +* The access to public host classes. +* The loading of new host classes by adding entries to the class path. +* Exporting new members into the polyglot bindings. +* Unrestricted IO operations on host system. +* Passing experimental options. +* The creation and use of new sub-processes. +* The access to process environment variables. + +This can be customized via overloaded `PolyglotScriptExecutor` constructor which accepts a `org.graalvm.polyglot.Context.Builder`. + +The usage of JavaScript in the framework components remains the same, on ly the difference that now it is going to work only on GraalVM with installed `js` component. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 332703e7a3..c311405043 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -63,6 +63,13 @@ See <<./sftp.adoc#sftp,SFTP Adapters>> for more information. Enabling observation for timers and tracing using Micrometer is now supported. See <<./metrics.adoc#micrometer-observation,Micrometer Observation>> for more information. +[[x6.0-graalmv-polyglot]] +==== GraalVM Polyglot Support + +The Scripting module now provides a `PolyglotScriptExecutor` implementation based on the GraalVM Polyglot support. +JavaScript support is now based on this executor since its JSR223 implementation has been removed from Java by itself. +See <<./scripting.adoc#scripting,Scripting Support>> for more information. + [[x6.0-general]] === General Changes