diff --git a/build.gradle b/build.gradle index 01067d2dc5..52a22262ab 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,7 @@ subprojects { subproject -> springSecurityVersion = project.hasProperty('springSecurityVersion') ? project.springSecurityVersion : '4.0.3.RELEASE' springSocialTwitterVersion = '1.1.1.RELEASE' springRetryVersion = '1.1.2.RELEASE' - springVersion = project.hasProperty('springVersion') ? project.springVersion : '4.2.4.RELEASE' + springVersion = project.hasProperty('springVersion') ? project.springVersion : '4.3.0.BUILD-SNAPSHOT' springWsVersion = '2.2.4.RELEASE' xmlUnitVersion = '1.6' xstreamVersion = '1.4.7' diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java index 579483eab2..4c516b3740 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -20,8 +20,10 @@ import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer; + +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor; @@ -40,6 +42,7 @@ import groovy.lang.GroovyObject; import groovy.lang.MetaClass; import groovy.lang.MissingPropertyException; import groovy.lang.Script; +import groovy.transform.CompileStatic; /** * The {@link org.springframework.integration.handler.MessageProcessor} implementation @@ -52,7 +55,8 @@ import groovy.lang.Script; * @author Artem Bilan * @since 2.0 */ -public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor { +public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor + implements InitializingBean { private final VariableBindingGroovyObjectCustomizerDecorator customizerDecorator = new VariableBindingGroovyObjectCustomizerDecorator(); @@ -65,10 +69,13 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti private volatile Class scriptClass; + private boolean compileStatic; + + private CompilerConfiguration compilerConfiguration; + /** * Create a processor for the given {@link ScriptSource} that will use a * DefaultScriptVariableGenerator. - * * @param scriptSource The script source. */ public GroovyScriptExecutingMessageProcessor(ScriptSource scriptSource) { @@ -79,43 +86,69 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti /** * Create a processor for the given {@link ScriptSource} that will use the provided * ScriptVariableGenerator. - * * @param scriptSource The script source. * @param scriptVariableGenerator The variable generator. */ - public GroovyScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptVariableGenerator scriptVariableGenerator) { + public GroovyScriptExecutingMessageProcessor(ScriptSource scriptSource, + ScriptVariableGenerator scriptVariableGenerator) { super(scriptVariableGenerator); this.scriptSource = scriptSource; } - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - super.setBeanClassLoader(classLoader); - this.groovyClassLoader = new GroovyClassLoader(classLoader); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - super.setBeanFactory(beanFactory); - if (beanFactory != null && beanFactory instanceof ConfigurableListableBeanFactory) { - ((ConfigurableListableBeanFactory) beanFactory).ignoreDependencyType(MetaClass.class); - } - } - /** * Sets a {@link GroovyObjectCustomizer} for this processor. - * * @param customizer The customizer. */ public void setCustomizer(GroovyObjectCustomizer customizer) { this.customizerDecorator.setCustomizer(customizer); } + /** + * Specify the {@code boolean} flag to indicate if the {@link GroovyClassLoader}'s compiler + * should be customised for the {@link CompileStatic} hint for the provided script. + *

More compiler options can be provided via {@link #setCompilerConfiguration(CompilerConfiguration)} + * overriding this flag. + * @param compileStatic the compile static {@code boolean} flag. + * @since 4.3 + * @see CompileStatic + */ + public void setCompileStatic(boolean compileStatic) { + this.compileStatic = compileStatic; + } + + /** + * Specify the {@link CompilerConfiguration} options to customize the Groovy script compilation. + * For example the {@link CompileStatic} and {@link org.codehaus.groovy.control.customizers.ImportCustomizer} + * are the most popular options. + * @param compilerConfiguration the Groovy script compiler options to use. + * @since 4.3 + * @see CompileStatic + * @see GroovyClassLoader + */ + public void setCompilerConfiguration(CompilerConfiguration compilerConfiguration) { + this.compilerConfiguration = compilerConfiguration; + } + @Override protected ScriptSource getScriptSource(Message message) { return this.scriptSource; } + @Override + public void afterPropertiesSet() throws Exception { + if (this.beanFactory != null && this.beanFactory instanceof ConfigurableListableBeanFactory) { + ((ConfigurableListableBeanFactory) this.beanFactory).ignoreDependencyType(MetaClass.class); + } + + CompilerConfiguration compilerConfiguration = this.compilerConfiguration; + if (compilerConfiguration == null && this.compileStatic) { + compilerConfiguration = new CompilerConfiguration(); + compilerConfiguration.addCompilationCustomizers(new ASTTransformationCustomizer(CompileStatic.class)); + } + + this.groovyClassLoader = new GroovyClassLoader(this.beanClassLoader, compilerConfiguration); + } + @Override protected Object executeScript(ScriptSource scriptSource, Map variables) throws Exception { Assert.notNull(scriptSource, "scriptSource must not be null"); @@ -182,8 +215,8 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti return super.getVariable(name); } catch (MissingPropertyException e) { - // Original {@link Binding} doesn't have 'variable' for the given 'name'. - // Try to resolve it as 'bean' from the given beanFactory. + // Original {@link Binding} doesn't have 'variable' for the given 'name'. + // Try to resolve it as 'bean' from the given beanFactory. } if (GroovyScriptExecutingMessageProcessor.this.beanFactory == null) { diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java index 5ead6352db..099ae252df 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -16,13 +16,15 @@ package org.springframework.integration.groovy.config; -import groovy.lang.Script; +import org.w3c.dom.Element; + import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.groovy.GroovyScriptExecutingMessageProcessor; import org.springframework.integration.scripting.config.AbstractScriptParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.w3c.dom.Element; + +import groovy.lang.Script; /** * Parser for the <groovy:script/> element. @@ -51,6 +53,8 @@ public class GroovyScriptParser extends AbstractScriptParser { protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "customizer"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "compiler-configuration"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "compile-static"); } diff --git a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-4.3.xsd b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-4.3.xsd index 2b3e3d91fa..1683b53425 100644 --- a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-4.3.xsd +++ b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-4.3.xsd @@ -36,6 +36,30 @@ + + + + Indicates if the target Groovy script should be compiled statically. + The @CompileStatic hint is applied for the Groovy compiler. + This attribute is ignored if the 'compiler-configuration' reference is specified. + + + + + + + + + + Reference to a CompilerConfiguration bean to be applied to the underlying GroovyClassLoader + for this script compilation. + + + + + + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests-context.xml b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests-context.xml index 69ddf46bb9..aab5bfe418 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests-context.xml +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests-context.xml @@ -1,20 +1,29 @@ + + - + + return payload == 'good' + ]]> + + + + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.groovy b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.groovy index 97a32ce063..d92b646faa 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.groovy +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.groovy @@ -1 +1,2 @@ -headers.type == 'good' \ No newline at end of file +// org.springframework.integration.groovy.config.GroovyFilterTests.TestConfig +headers.type == pi diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java index 9928948642..38176407d7 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -16,16 +16,25 @@ package org.springframework.integration.groovy.config; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.MultipleCompilationErrorsException; +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer; +import org.codehaus.groovy.control.customizers.ImportCustomizer; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.filter.MessageFilter; @@ -37,9 +46,14 @@ import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessageHandlingException; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.stereotype.Component; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import groovy.transform.CompileStatic; + /** * @author Mark Fisher * @author Artem Bilan @@ -55,6 +69,9 @@ public class GroovyFilterTests { @Autowired private MessageChannel inlineScriptInput; + @Autowired + private MessageChannel compileStaticFailScriptInput; + @Autowired @Qualifier("groovyFilter.handler") private MessageHandler groovyFilterMessageHandler; @@ -69,7 +86,7 @@ public class GroovyFilterTests { .build(); Message message2 = MessageBuilder.withPayload("test-2") .setReplyChannel(replyChannel) - .setHeader("type", "good") + .setHeader("type", Math.PI) .build(); this.referencedScriptInput.send(message1); this.referencedScriptInput.send(message2); @@ -103,4 +120,31 @@ public class GroovyFilterTests { assertTrue(messageProcessor instanceof GroovyScriptExecutingMessageProcessor); } + @Test + public void testCompileStaticIsApplied() { + try { + this.compileStaticFailScriptInput.send(new GenericMessage("foo")); + fail("MultipleCompilationErrorsException expected"); + } + catch (Exception e) { + assertThat(e, instanceOf(MessageHandlingException.class)); + assertThat(e.getCause(), instanceOf(MultipleCompilationErrorsException.class)); + assertThat(e.getMessage(), containsString("[Static type checking] - The variable [payload] is undeclared.")); + } + } + + @Component + public static class TestConfig { + + @Bean + public CompilerConfiguration compilerConfiguration() { + CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); + ImportCustomizer importCustomizer = new ImportCustomizer(); + importCustomizer.addStaticImport("pi", "java.lang.Math", "PI"); // import static java.lang.Math.PI as pi + compilerConfiguration.addCompilationCustomizers(importCustomizer); + return compilerConfiguration; + } + + } + } diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests-context.xml b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests-context.xml index 3a978ce952..aa4e5f5f98 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests-context.xml +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests-context.xml @@ -6,7 +6,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/groovy http://www.springframework.org/schema/integration/groovy/spring-integration-groovy.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> - + @@ -16,7 +16,7 @@ - + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests.java index bc5ceba157..71b7d0cbbe 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRefreshTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2016 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. @@ -36,6 +36,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Fisher + * @author Artem Bilan * @since 2.0 */ @ContextConfiguration @@ -66,7 +67,8 @@ public class GroovyRefreshTests { private static class CycleResource extends AbstractResource { private int count = -1; - private String[] scripts = {"\"groovy-$payload-0\"", "\"groovy-$payload-1\""}; + private String[] scripts = {"\"groovy-${binding.variables['payload']}-0\"", + "\"groovy-${binding.variables['payload']}-1\""}; public String getDescription() { return "CycleResource"; diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests-context.xml b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests-context.xml index 2138b27944..41e1b866c5 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests-context.xml +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests-context.xml @@ -12,8 +12,8 @@ - diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.groovy b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.groovy index 411be5f59b..17db19d431 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.groovy +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.groovy @@ -1 +1,6 @@ -"groovy-$payload" \ No newline at end of file +@groovy.transform.CompileStatic +String transform(Object payload) { + "groovy-$payload" +} + +transform(payload) diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/RefreshableResourceScriptSource.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/RefreshableResourceScriptSource.java index 5348100d24..9d89f7f4cf 100644 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/RefreshableResourceScriptSource.java +++ b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/RefreshableResourceScriptSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -60,7 +60,7 @@ public class RefreshableResourceScriptSource implements ScriptSource { } public String suggestedClassName() { - return this.source.suggestedClassName(); + return this.source.getResource().getFilename(); } public boolean isModified() { diff --git a/src/reference/asciidoc/groovy.adoc b/src/reference/asciidoc/groovy.adoc index e156346f4e..5f261cd755 100644 --- a/src/reference/asciidoc/groovy.adoc +++ b/src/reference/asciidoc/groovy.adoc @@ -2,7 +2,7 @@ === Groovy support In Spring Integration 2.0 we added Groovy support allowing you to use the Groovy scripting language to provide the logic for various integration components similar to the way the Spring Expression Language (SpEL) is supported for routing, transformation and other integration concerns. -For more information about Groovy please refer to the Groovy documentation which you can find on the http://groovy.codehaus.org[project website] +For more information about Groovy please refer to the Groovy documentation which you can find on the http://www.groovy-lang.org/[project website]. [[groovy-config]] ==== Groovy configuration @@ -32,7 +32,7 @@ Also note that the `lang` attribute on the `