From c3db043334d25c855bc820b98d091235647e9435 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 27 Jan 2011 16:05:37 -0500 Subject: [PATCH] INT-1727 added namespace support for Groovy binding variables --- .../groovy/DefaultScriptVariableSource.java | 24 ++---- .../groovy/GroovyCommandMessageProcessor.java | 15 ++++ ...GroovyScriptExecutingMessageProcessor.java | 3 +- .../groovy/MapResolvingBindingCustomizer.java | 4 +- .../config/GroovyControlBusFactoryBean.java | 24 +++++- .../groovy/config/GroovyScriptParser.java | 84 ++++++++++++++----- .../config/spring-integration-groovy-2.0.xsd | 72 +++++++++++----- .../GroovyServiceActivatorTests-context.xml | 16 +++- ...oovyServiceActivatorTests-fail-context.xml | 23 +++++ .../config/GroovyServiceActivatorTests.groovy | 2 +- .../config/GroovyServiceActivatorTests.java | 28 +++++-- 11 files changed, 220 insertions(+), 75 deletions(-) create mode 100644 spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-fail-context.xml diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/DefaultScriptVariableSource.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/DefaultScriptVariableSource.java index 222149930e..c41a7d090c 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/DefaultScriptVariableSource.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/DefaultScriptVariableSource.java @@ -22,12 +22,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.context.Lifecycle; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.integration.Message; -import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.util.CollectionUtils; -import org.springframework.util.CustomizableThreadCreator; /** * @author Oleg Zhurakousky @@ -60,19 +56,17 @@ public class DefaultScriptVariableSource implements BeanFactoryAware, ScriptVari } // Add contents of 'variableMap' if (!CollectionUtils.isEmpty(variableMap)){ - scriptVariables.putAll(variableMap); - } - // Add contents of 'beanFactory' - if (this.beanFactory != null){ - for (String name : this.beanFactory.getBeanDefinitionNames()) { - Object bean = this.beanFactory.getBean(name); - if (bean instanceof Lifecycle || bean instanceof CustomizableThreadCreator - || (AnnotationUtils.findAnnotation(bean.getClass(), ManagedResource.class) != null)) { - scriptVariables.put(name, bean); + for (String variableName : variableMap.keySet()) { + Object variableValue = variableMap.get(variableName); + if (variableValue == null){ + scriptVariables.put(variableName, this.beanFactory.getBean(variableName)); } - } + else { + scriptVariables.put(variableName, variableValue); + } + } } - + // custom logic this.doResolveScriptVariables(scriptVariables); return scriptVariables; } diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java index a8e5620262..bab9e8b08b 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java @@ -13,8 +13,11 @@ package org.springframework.integration.groovy; +import groovy.lang.GString; + import org.springframework.integration.Message; import org.springframework.scripting.ScriptSource; +import org.springframework.scripting.groovy.GroovyScriptFactory; import org.springframework.scripting.support.StaticScriptSource; import org.springframework.util.Assert; @@ -41,6 +44,18 @@ public class GroovyCommandMessageProcessor extends GroovyScriptExecutingMessageP String className = generateScriptName(message); return new StaticScriptSource((String) payload, className); } + + @Override + protected Object executeScript(ScriptSource scriptSource, Message message) throws Exception { + Assert.notNull(scriptSource, "scriptSource must not be null"); + MapResolvingBindingCustomizer bindingCustomizer = new MapResolvingBindingCustomizer(); + GroovyScriptFactory factory = new GroovyScriptFactory(this.getClass().getSimpleName(), bindingCustomizer); + if (this.scriptVariableSource != null){ + bindingCustomizer.setResolvedScriptVariables(this.scriptVariableSource.resolveScriptVariables(message)); + } + Object result = factory.getScriptedObject(scriptSource, null); + return (result instanceof GString) ? result.toString() : result; + } protected String generateScriptName(Message message) { // Don't use the same script (class) name for all invocations by default 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 1a35a6f6cc..1339bd3875 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 @@ -38,7 +38,7 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti private volatile ScriptSource scriptSource; - private final ScriptVariableSource scriptVariableSource; + protected final ScriptVariableSource scriptVariableSource; /** * Create a processor for the given {@link ScriptSource}. @@ -69,5 +69,4 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti return (result instanceof GString) ? result.toString() : result; } } - } diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/MapResolvingBindingCustomizer.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/MapResolvingBindingCustomizer.java index 305e2ced65..26a1da4d0c 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/MapResolvingBindingCustomizer.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/MapResolvingBindingCustomizer.java @@ -27,11 +27,11 @@ import org.springframework.util.Assert; * @author Oleg Zhurakousky * @since 2.0 */ -public class MapResolvingBindingCustomizer implements GroovyObjectCustomizer { +class MapResolvingBindingCustomizer implements GroovyObjectCustomizer { private volatile Map resolvedScriptVariables; - + public void setResolvedScriptVariables(Map resolvedScriptVariables) { this.resolvedScriptVariables = resolvedScriptVariables; } diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java index 7b96b6f843..a4e4d6861d 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java @@ -13,11 +13,17 @@ package org.springframework.integration.groovy.config; +import java.util.Map; + +import org.springframework.context.Lifecycle; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.groovy.DefaultScriptVariableSource; import org.springframework.integration.groovy.GroovyCommandMessageProcessor; import org.springframework.integration.handler.ServiceActivatingHandler; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.util.CustomizableThreadCreator; /** * FactoryBean for creating {@link MessageHandler} instances to handle a message as a Groovy Script. @@ -36,7 +42,7 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac @Override protected MessageHandler createHandler() { - DefaultScriptVariableSource scriptVariableSource = new DefaultScriptVariableSource(); + DefaultScriptVariableSource scriptVariableSource = new ManagedBeansScriptVariableSource(); scriptVariableSource.setBeanFactory(this.getBeanFactory()); GroovyCommandMessageProcessor processor = new GroovyCommandMessageProcessor(scriptVariableSource); return this.configureHandler(new ServiceActivatingHandler(processor)); @@ -48,4 +54,20 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac } return handler; } + + private class ManagedBeansScriptVariableSource extends DefaultScriptVariableSource { + @Override + protected void doResolveScriptVariables(Map variables){ + if (this.beanFactory != null){ + for (String name : this.beanFactory.getBeanDefinitionNames()) { + Object bean = this.beanFactory.getBean(name); + if (bean instanceof Lifecycle || + bean instanceof CustomizableThreadCreator || + (AnnotationUtils.findAnnotation(bean.getClass(), ManagedResource.class) != null)) { + variables.put(name, bean); + } + } + } + } + } } 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 b0052df1ff..9eabd8e5ad 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 @@ -16,6 +16,10 @@ package org.springframework.integration.groovy.config; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.w3c.dom.Element; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -23,12 +27,15 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.XmlReaderContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.scripting.support.StaticScriptSource; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; /** * @author Mark Fisher + * @author Oleg Zhurakousky * @since 2.0 */ public class GroovyScriptParser extends AbstractSingleBeanDefinitionParser { @@ -37,7 +44,6 @@ public class GroovyScriptParser extends AbstractSingleBeanDefinitionParser { private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay"; - @Override protected String getBeanClassName(Element element) { return "org.springframework.integration.groovy.GroovyScriptExecutingMessageProcessor"; @@ -45,36 +51,70 @@ public class GroovyScriptParser extends AbstractSingleBeanDefinitionParser { @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - builder.addConstructorArgValue(this.resolveScriptSource(element, parserContext.getReaderContext())); + String scriptLocation = element.getAttribute(LOCATION_ATTRIBUTE); + String scriptText = DomUtils.getTextValue(element); + if (!(StringUtils.hasText(scriptLocation) ^ StringUtils.hasText(scriptText))) { + parserContext.getReaderContext().error("Either the 'location' attribute or inline script text must be provided, but not both.", element); + return; + } + + List variableElements = DomUtils.getChildElementsByTagName(element, "variable"); + + if (StringUtils.hasText(scriptText) && variableElements.size() > 0){ + parserContext.getReaderContext().error("Variable bindings are not allowed when using inline groovy script. " + + "Specify location of the script via 'location' attribute instead", element); + return; + } + + if (StringUtils.hasText(scriptLocation)){ + builder.addConstructorArgValue(this.resolveScriptLocation(element, parserContext.getReaderContext(), scriptLocation)); + } + else { + builder.addConstructorArgValue(new StaticScriptSource(scriptText, "groovy.lang.Script")); + } + BeanDefinitionBuilder scriptVariableSource = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.integration.groovy.DefaultScriptVariableSource"); String name = BeanDefinitionReaderUtils.registerWithGeneratedName(scriptVariableSource.getBeanDefinition(), parserContext.getRegistry()); builder.addConstructorArgReference(name); - } - - private Object resolveScriptSource(Element element, XmlReaderContext readerContext) { - boolean hasScriptLocation = element.hasAttribute(LOCATION_ATTRIBUTE); - String scriptText = DomUtils.getTextValue(element); - if (!(hasScriptLocation ^ StringUtils.hasText(scriptText))) { - readerContext.error("Either the 'location' attribute or inline script text must be provided, but not both.", element); - return null; - } - else if (hasScriptLocation) { - String refreshDelayText = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); - String beanClassName = "org.springframework.integration.groovy.config.RefreshableResourceScriptSource"; - BeanDefinitionBuilder resourceScriptSourceBuilder = - BeanDefinitionBuilder.genericBeanDefinition(beanClassName); - resourceScriptSourceBuilder.addConstructorArgValue(element.getAttribute(LOCATION_ATTRIBUTE)); - if (StringUtils.hasText(refreshDelayText)) { - resourceScriptSourceBuilder.addConstructorArgValue(refreshDelayText); + + Map variableMap = new HashMap(); + for (Element childElement : variableElements) { + String variableName = childElement.getAttribute("name"); + String variableValue = childElement.getAttribute("value"); + String variableRef = childElement.getAttribute("ref"); + if (StringUtils.hasText(variableValue) && StringUtils.hasText(variableRef)){ + parserContext.getReaderContext().error("Exactly one of the 'ref' attribute or 'value' attribute, " + + " is required for element " + + IntegrationNamespaceUtils.createElementDescription(element) + ".", element); + } + if (StringUtils.hasText(variableValue)){ + variableMap.put(variableName, variableValue); } else { - resourceScriptSourceBuilder.addConstructorArgValue(-1L); + // 'null' means that the value will be retrieved from the AC + variableMap.put(variableName, null); } - return resourceScriptSourceBuilder.getBeanDefinition(); } - return new StaticScriptSource(scriptText, "groovy.lang.Script"); + if (!CollectionUtils.isEmpty(variableMap)){ + scriptVariableSource.addConstructorArgValue(variableMap); + } + } + + private Object resolveScriptLocation(Element element, XmlReaderContext readerContext, String scriptLocation) { + String refreshDelayText = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); + String beanClassName = "org.springframework.integration.groovy.config.RefreshableResourceScriptSource"; + BeanDefinitionBuilder resourceScriptSourceBuilder = + BeanDefinitionBuilder.genericBeanDefinition(beanClassName); + resourceScriptSourceBuilder.addConstructorArgValue(scriptLocation); + if (StringUtils.hasText(refreshDelayText)) { + resourceScriptSourceBuilder.addConstructorArgValue(refreshDelayText); + } + else { + resourceScriptSourceBuilder.addConstructorArgValue(-1L); + } + return resourceScriptSourceBuilder.getBeanDefinition(); } } diff --git a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd index 33365f30fa..5f02754019 100644 --- a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd +++ b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd @@ -13,29 +13,55 @@ Configures an inner bean that will generate a Groovy Script. - - - - - - - Resource location path for the Script. Either this or an inline script - as body text should be - provided, but not both. - - - - - - - Refresh delay for the script contents if specified as a resource - location (defaults to never - refresh). - - - - - + + + + + + + + Name of the groovy binding variable + + + + + + + Value of the groovy binding variable + + + + + + + Value of the groovy binding variable pointing to a bean reference + + + + + + + + + + + + + Resource location path for the Script. Either this or an inline script + as body text should be + provided, but not both. + + + + + + + Refresh delay for the script contents if specified as a resource + location (defaults to never + refresh). + + + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-context.xml b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-context.xml index 94001f2757..2b11a9b6ae 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-context.xml +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-context.xml @@ -6,18 +6,26 @@ 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/integration/groovy/spring-integration-groovy-2.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> - + + + + + - + + ]]> + + + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-fail-context.xml b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-fail-context.xml new file mode 100644 index 0000000000..25d8a98d2d --- /dev/null +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests-fail-context.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy index 411be5f59b..11b09a4d3c 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy @@ -1 +1 @@ -"groovy-$payload" \ No newline at end of file +"groovy-$payload-" + "$foo" + " - " + bar + " - " + date \ No newline at end of file diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java index db838b1bc8..8f844e391d 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java @@ -16,6 +16,8 @@ package org.springframework.integration.groovy.config; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -23,6 +25,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.channel.QueueChannel; @@ -46,21 +50,30 @@ public class GroovyServiceActivatorTests { @Test - public void referencedScript() { + public void referencedScript() throws Exception{ QueueChannel replyChannel = new QueueChannel(); replyChannel.setBeanName("returnAddress"); for (int i = 1; i <= 3; i++) { Message message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build(); this.referencedScriptInput.send(message); + Thread.sleep(1000); } - assertEquals("groovy-test-1", replyChannel.receive(0).getPayload()); - assertEquals("groovy-test-2", replyChannel.receive(0).getPayload()); - assertEquals("groovy-test-3", replyChannel.receive(0).getPayload()); + String value1 = (String) replyChannel.receive(0).getPayload(); + String value2 = (String) replyChannel.receive(0).getPayload(); + String value3 = (String) replyChannel.receive(0).getPayload(); + assertTrue(value1.startsWith("groovy-test-1-foo - bar")); + assertTrue(value2.startsWith("groovy-test-2-foo - bar")); + assertTrue(value3.startsWith("groovy-test-3-foo - bar")); + // becouse we are using 'prototype bean the suffix date will be different + + assertFalse(value1.substring(26).equals(value2.substring(26))); + assertFalse(value2.substring(26).equals(value3.substring(26))); + assertNull(replyChannel.receive(0)); } @Test - public void inlineScript() { + public void inlineScript() throws Exception{ QueueChannel replyChannel = new QueueChannel(); replyChannel.setBeanName("returnAddress"); for (int i = 1; i <= 3; i++) { @@ -72,5 +85,10 @@ public class GroovyServiceActivatorTests { assertEquals("inline-test-3", replyChannel.receive(0).getPayload()); assertNull(replyChannel.receive(0)); } + + @Test(expected=BeanDefinitionParsingException.class) + public void inlineScriptAndVariables() throws Exception{ + new ClassPathXmlApplicationContext("GroovyServiceActivatorTests-fail-context.xml", this.getClass()); + } }