INT-1727 added namespace support for Groovy binding variables

This commit is contained in:
Oleg Zhurakousky
2011-01-27 16:05:37 -05:00
parent f80df192c8
commit c3db043334
11 changed files with 220 additions and 75 deletions

View File

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

View File

@@ -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

View File

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

View File

@@ -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<String, ?> resolvedScriptVariables;
public void setResolvedScriptVariables(Map<String, ?> resolvedScriptVariables) {
this.resolvedScriptVariables = resolvedScriptVariables;
}

View File

@@ -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<String, Object> 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);
}
}
}
}
}
}

View File

@@ -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<Element> 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<String, Object> variableMap = new HashMap<String, Object>();
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();
}
}

View File

@@ -13,29 +13,55 @@
Configures an inner bean that will generate a Groovy Script.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="location">
<xsd:annotation>
<xsd:documentation>
Resource location path for the Script. Either this or an inline script
as body text should be
provided, but not both.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="refresh-check-delay">
<xsd:annotation>
<xsd:documentation>
Refresh delay for the script contents if specified as a resource
location (defaults to never
refresh).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:simpleContent>
<xsd:complexType mixed="true">
<xsd:sequence>
<xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Name of the groovy binding variable
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="value" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Value of the groovy binding variable
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ref" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Value of the groovy binding variable pointing to a bean reference
</xsd:documentation>
<xsd:appinfo>
<tool:expected-type type="java.lang.Object"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="location">
<xsd:annotation>
<xsd:documentation>
Resource location path for the Script. Either this or an inline script
as body text should be
provided, but not both.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="refresh-check-delay">
<xsd:annotation>
<xsd:documentation>
Refresh delay for the script contents if specified as a resource
location (defaults to never
refresh).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

View File

@@ -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">
<service-activator input-channel="referencedScriptInput">
<groovy:script location="org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy"/>
<groovy:script location="org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy">
<groovy:variable name="foo" value="foo"/>
<groovy:variable name="bar" value="bar"/>
<groovy:variable name="date" ref="date"/>
</groovy:script>
</service-activator>
<service-activator input-channel="inlineScriptInput">
<groovy:script><![CDATA[
<groovy:script>
<![CDATA[
return "inline-$payload"
]]></groovy:script>
]]>
</groovy:script>
</service-activator>
<beans:bean id="date" class="java.util.Date" scope="prototype"/>
</beans:beans>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:groovy="http://www.springframework.org/schema/integration/groovy"
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-2.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<service-activator input-channel="inlineScriptInput">
<groovy:script>
<![CDATA[
return "inline-$payload-" + "$foo" + " - " + bar + " - " + date
]]>
<groovy:variable name="foo" value="foo"/>
<groovy:variable name="bar" value="bar"/>
</groovy:script>
</service-activator>
</beans:beans>

View File

@@ -1 +1 @@
"groovy-$payload"
"groovy-$payload-" + "$foo" + " - " + bar + " - " + date

View File

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