Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -600,11 +600,14 @@ project('spring-integration-scripting') {
description = 'Spring Integration Scripting Support'
dependencies {
compile project(":spring-integration-core")
compile ('org.jetbrains.kotlin:kotlin-script-util', optional)
compile ('org.jetbrains.kotlin:kotlin-compiler-embeddable', optional)

testCompile("org.jruby:jruby-complete:$jrubyVersion")
testCompile("org.codehaus.groovy:groovy:$groovyVersion")
testCompile("org.codehaus.groovy:groovy-jsr223:$groovyVersion")
testCompile("org.python:jython-standalone:$jythonVersion")
testRuntime 'org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable'
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,8 +26,11 @@
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 {
Expand All @@ -50,38 +50,12 @@ protected void postProcess(BeanDefinitionBuilder builder, Element element, Parse
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

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;
Expand All @@ -32,6 +31,7 @@
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;

/**
Expand Down Expand Up @@ -99,21 +99,16 @@ public void afterPropertiesSet() {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,33 @@
* @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 ScriptEngine scriptEngine;
protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR - final

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());
}
}

protected AbstractScriptExecutor(ScriptEngine scriptEngine) {
Assert.notNull(scriptEngine, "'scriptEngine' must not be null.");
this.scriptEngine = scriptEngine;
}

public ScriptEngine getScriptEngine() {
return this.scriptEngine;
}

@Override
public Object executeScript(ScriptSource scriptSource, Map<String, Object> variables) {
Object result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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;


/**
* @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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,22 @@

package org.springframework.integration.scripting.jsr223;

import org.springframework.util.ClassUtils;


/**
* @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");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a private CTOR.


public static ScriptExecutor getScriptExecutor(String language) {
if (language.equalsIgnoreCase("python") || language.equalsIgnoreCase("jython")) {
Expand All @@ -31,7 +39,32 @@ public static ScriptExecutor getScriptExecutor(String language) {
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() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a private CTOR

Doesn't this work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry; missed that 😦

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it needs super() to prevent Sonar from complaining.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Will add. Can we tweak Sonar do not complain on the matter?
There is definitely a super() in the target byte code, so it's complaining is pointless.

Or do I miss something else ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding super is a no-op - the compiler generates one anyway.

I don't think we want to disable empty block detection.

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -152,7 +153,7 @@ public void serviceWithRefreshCheckDelayTest() throws IOException {
}

@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"));
Expand All @@ -166,7 +167,7 @@ public void routerTest() throws IOException {
}

@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();
Expand All @@ -180,14 +181,28 @@ public void messageSourceTest() throws IOException, InterruptedException {
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 {

@Value("scripts/TesSplitterScript.groovy")
private Resource splitterScript;

@Value("scripts/TestFilterScript.groovy")
@Value("scripts/TestFilterScript.kts")
private Resource filterScript;

@Bean
Expand Down Expand Up @@ -244,12 +259,21 @@ public PollableChannel shortStrings() {
@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());
}


}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Loading