INT-2567: Improvement & refactor Groovy component
Introduce VariableResolver, BeanFilter, FilteredBeanFactoryDecorator, GroovyVariableResolverBinding, refactoring ManagedBeansScriptVariableGenerator for usage GroovyVariableResolverBinding. GroovyVariableResolverBindingTests. additional tests for GroovyControlBus. add 'spring-web' dependency for tests about 'request' custom scope. Manual: change description about 'Groovy Control Bus' scripts limitation'. INT-2567: Improve & refacror Groovy Control Bus Introduce separate BindingOverwriteGroovyObjectCustomizerDecorator Internal GroovyControlBusFactoryBean$ManagedBeansBinding with delegation to provided BeanFactory Inline ScriptVariableGenerator implementation just for Message 'headers' variable Tests for new GroovyScriptPayloadMessageProcessor logic about binding Integration tests for managed beans in the custom scope Reference Manual: polishing description about Control Bus' behavior INT-2567 polishing added @link to javadocs
This commit is contained in:
committed by
Oleg Zhurakousky
parent
5750c69142
commit
74b4638eb9
@@ -334,6 +334,7 @@ project('spring-integration-groovy') {
|
||||
compile "org.codehaus.groovy:groovy-all:$groovyVersion"
|
||||
compile "org.springframework:spring-context-support:$springVersion"
|
||||
testCompile project(":spring-integration-test")
|
||||
testCompile "org.springframework:spring-web:$springVersion"
|
||||
}
|
||||
bundlor {
|
||||
bundleSymbolicName = 'org.springframework.integration.groovy'
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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
|
||||
*
|
||||
* http://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.groovy;
|
||||
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.GroovyObject;
|
||||
import groovy.lang.Script;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @since 2.2
|
||||
*/
|
||||
|
||||
class BindingOverwriteGroovyObjectCustomizerDecorator extends VariableBindingGroovyObjectCustomizerDecorator {
|
||||
|
||||
private final Binding binding;
|
||||
|
||||
BindingOverwriteGroovyObjectCustomizerDecorator(Binding binding) {
|
||||
Assert.notNull(binding, "binding must not be null");
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(GroovyObject goo) {
|
||||
Assert.state(goo instanceof Script, "Expected a Script");
|
||||
((Script) goo).setBinding(this.binding);
|
||||
super.customize(goo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Copyright 2002-2012 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
|
||||
*
|
||||
*
|
||||
* http://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.
|
||||
@@ -13,12 +13,14 @@
|
||||
|
||||
package org.springframework.integration.groovy;
|
||||
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.GString;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor;
|
||||
import org.springframework.integration.scripting.DefaultScriptVariableGenerator;
|
||||
import org.springframework.integration.scripting.ScriptVariableGenerator;
|
||||
import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
|
||||
@@ -31,27 +33,52 @@ import org.springframework.util.CollectionUtils;
|
||||
* @author Dave Syer
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GroovyCommandMessageProcessor extends AbstractScriptExecutingMessageProcessor<Object> {
|
||||
|
||||
private volatile GroovyObjectCustomizer customizer;
|
||||
|
||||
private Binding binding;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a GroovyCommandMessageProcessor that will use the DefaultScriptVariableGenerator.
|
||||
* Creates a {@link GroovyCommandMessageProcessor} that will use the {@link DefaultScriptVariableGenerator}.
|
||||
*/
|
||||
public GroovyCommandMessageProcessor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GroovyCommandMessageProcessor that will use the provided ScriptVariableGenerator.
|
||||
* Creates a {@link GroovyCommandMessageProcessor} that will use the provided {@link ScriptVariableGenerator}.
|
||||
*/
|
||||
public GroovyCommandMessageProcessor(ScriptVariableGenerator scriptVariableGenerator) {
|
||||
super(scriptVariableGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link GroovyCommandMessageProcessor} that will use the {@link DefaultScriptVariableGenerator}
|
||||
* and provided {@link Binding}.
|
||||
* Provided 'binding' will be used in the {@link BindingOverwriteGroovyObjectCustomizerDecorator} to overwrite
|
||||
* original Groovy Script 'binding'.
|
||||
*/
|
||||
public GroovyCommandMessageProcessor(Binding binding) {
|
||||
this();
|
||||
Assert.notNull(binding, "binding must not be null");
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link GroovyCommandMessageProcessor} that will use the provided {@link ScriptVariableGenerator} and Binding.
|
||||
* Provided 'binding' will be used in the {@link BindingOverwriteGroovyObjectCustomizerDecorator} to overwrite
|
||||
* original Groovy Script 'binding'.
|
||||
*/
|
||||
public GroovyCommandMessageProcessor(Binding binding, ScriptVariableGenerator scriptVariableGenerator) {
|
||||
this(scriptVariableGenerator);
|
||||
Assert.notNull(binding, "binding must not be null");
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link GroovyObjectCustomizer} for this processor.
|
||||
@@ -71,20 +98,23 @@ public class GroovyCommandMessageProcessor extends AbstractScriptExecutingMessag
|
||||
@Override
|
||||
protected Object executeScript(ScriptSource scriptSource, Map<String, Object> variables) throws Exception {
|
||||
Assert.notNull(scriptSource, "scriptSource must not be null");
|
||||
VariableBindingGroovyObjectCustomizerDecorator customizerDecorator = new VariableBindingGroovyObjectCustomizerDecorator();
|
||||
VariableBindingGroovyObjectCustomizerDecorator customizerDecorator = this.binding != null
|
||||
? new BindingOverwriteGroovyObjectCustomizerDecorator(this.binding)
|
||||
: new VariableBindingGroovyObjectCustomizerDecorator();
|
||||
if (this.customizer != null) {
|
||||
customizerDecorator.setCustomizer(this.customizer);
|
||||
}
|
||||
GroovyScriptFactory factory = new GroovyScriptFactory(this.getClass().getSimpleName(), customizerDecorator);
|
||||
if (!CollectionUtils.isEmpty(variables)) {
|
||||
customizerDecorator.setVariables(variables);
|
||||
}
|
||||
GroovyScriptFactory factory = new GroovyScriptFactory(this.getClass().getSimpleName(), customizerDecorator);
|
||||
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
|
||||
return getClass().getSimpleName() + message.getHeaders().getId().toString().replaceAll("-", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Copyright 2002-2012 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
|
||||
*
|
||||
*
|
||||
* http://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.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Copyright 2002-2012 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
|
||||
*
|
||||
*
|
||||
* http://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.
|
||||
@@ -16,8 +16,11 @@ package org.springframework.integration.groovy.config;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.MissingPropertyException;
|
||||
import org.springframework.beans.factory.BeanCreationNotAllowedException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -33,10 +36,11 @@ import org.springframework.util.CustomizableThreadCreator;
|
||||
|
||||
/**
|
||||
* FactoryBean for creating {@link MessageHandler} instances to handle a message as a Groovy Script.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFactoryBean<MessageHandler> {
|
||||
@@ -45,7 +49,6 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac
|
||||
|
||||
private volatile GroovyObjectCustomizer customizer;
|
||||
|
||||
|
||||
public void setSendTimeout(Long sendTimeout) {
|
||||
this.sendTimeout = sendTimeout;
|
||||
}
|
||||
@@ -56,8 +59,15 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler() {
|
||||
ManagedBeansScriptVariableGenerator scriptVariableGenerator = new ManagedBeansScriptVariableGenerator(this.getBeanFactory());
|
||||
GroovyCommandMessageProcessor processor = new GroovyCommandMessageProcessor(scriptVariableGenerator);
|
||||
Binding binding = new ManagedBeansBinding(this.getBeanFactory());
|
||||
GroovyCommandMessageProcessor processor = new GroovyCommandMessageProcessor(binding, new ScriptVariableGenerator() {
|
||||
@Override
|
||||
public Map<String, Object> generateScriptVariables(Message<?> message) {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
variables.put("headers", message.getHeaders());
|
||||
return variables;
|
||||
}
|
||||
});
|
||||
if (this.customizer != null) {
|
||||
processor.setCustomizer(this.customizer);
|
||||
}
|
||||
@@ -71,39 +81,48 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
private static class ManagedBeansScriptVariableGenerator implements ScriptVariableGenerator {
|
||||
/**
|
||||
* Bridge {@link Binding} implementation which uses <code>beanFactory</code>
|
||||
* to resolve Groovy variable as delegate if the last one isn't contained
|
||||
* in the original Groovy script {@link Binding}.
|
||||
* In additionally beans should be 'managed' with specific properties which
|
||||
* are allowed in the Control Bus operations.
|
||||
*/
|
||||
private static class ManagedBeansBinding extends Binding {
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
public ManagedBeansScriptVariableGenerator(BeanFactory beanFactory) {
|
||||
public ManagedBeansBinding(BeanFactory beanFactory) {
|
||||
this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory)
|
||||
? (ConfigurableListableBeanFactory) beanFactory : null;
|
||||
}
|
||||
|
||||
public Map<String, Object> generateScriptVariables(Message<?> message) {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
variables.put("headers", message.getHeaders());
|
||||
if (this.beanFactory != null) {
|
||||
for (String name : this.beanFactory.getBeanDefinitionNames()) {
|
||||
if (!this.beanFactory.getBeanDefinition(name).isAbstract()) {
|
||||
try {
|
||||
Object bean = this.beanFactory.getBean(name);
|
||||
if (bean instanceof Lifecycle ||
|
||||
bean instanceof CustomizableThreadCreator ||
|
||||
(AnnotationUtils.findAnnotation(bean.getClass(), ManagedResource.class) != null)) {
|
||||
variables.put(name, bean);
|
||||
}
|
||||
}
|
||||
catch (BeanCreationException e) {
|
||||
// might be a custom scope bean lacking required context
|
||||
// ignore, and continue the loop iteration
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Object getVariable(String name) {
|
||||
try {
|
||||
return super.getVariable(name);
|
||||
}
|
||||
catch (MissingPropertyException e) {
|
||||
// Original {@link Binding} doesn't have 'variable' for the given 'name'.
|
||||
// Try to resolve it as 'managed bean' from the given <code>beanFactory</code>.
|
||||
}
|
||||
if (this.beanFactory == null) {
|
||||
throw new MissingPropertyException(name, this.getClass());
|
||||
}
|
||||
BeanDefinition def = this.beanFactory.getBeanDefinition(name);
|
||||
if (!def.isAbstract() && !def.isPrototype()) {
|
||||
Object bean = this.beanFactory.getBean(name);
|
||||
if (bean instanceof Lifecycle ||
|
||||
bean instanceof CustomizableThreadCreator ||
|
||||
(AnnotationUtils.findAnnotation(bean.getClass(), ManagedResource.class) != null)) {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
return variables;
|
||||
throw new BeanCreationNotAllowedException(name, "Only beans with @ManagedResource or beans which implement " +
|
||||
"org.springframework.context.Lifecycle or org.springframework.util.CustomizableThreadCreator " +
|
||||
"are allowed to use as ControlBus components.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Copyright 2002-2012 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
|
||||
*
|
||||
*
|
||||
* http://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.
|
||||
@@ -21,7 +21,10 @@ import org.springframework.integration.config.xml.AbstractConsumerEndpointParser
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
|
||||
/**
|
||||
* Parser for the <groovy:control-bus/> element.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GroovyControlBusParser extends AbstractConsumerEndpointParser {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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,24 +16,29 @@
|
||||
|
||||
package org.springframework.integration.groovy.config;
|
||||
|
||||
import groovy.lang.Script;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Parser for the <groovy:script/> element.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GroovyScriptParser extends AbstractScriptParser {
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected String getBeanClassName(Element element) {
|
||||
return "org.springframework.integration.groovy.GroovyScriptExecutingMessageProcessor";
|
||||
return GroovyScriptExecutingMessageProcessor.class.getName();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -41,14 +46,14 @@ public class GroovyScriptParser extends AbstractScriptParser {
|
||||
*/
|
||||
@Override
|
||||
protected String getScriptSourceClassName() {
|
||||
return "groovy.lang.Script";
|
||||
return Script.class.getName();
|
||||
}
|
||||
|
||||
|
||||
protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "customizer");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Copyright 2002-2012 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
|
||||
*
|
||||
*
|
||||
* http://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.
|
||||
@@ -14,10 +14,15 @@
|
||||
package org.springframework.integration.groovy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.MissingPropertyException;
|
||||
import junit.framework.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.integration.Message;
|
||||
@@ -29,6 +34,7 @@ import org.springframework.test.annotation.Repeat;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GroovyScriptPayloadMessageProcessorTests {
|
||||
@@ -65,11 +71,53 @@ public class GroovyScriptPayloadMessageProcessorTests {
|
||||
public void testSimpleExecutionWithContext() throws Exception {
|
||||
Message<?> message = MessageBuilder.withPayload("\"spam is $spam foo is $headers.foo\"")
|
||||
.setHeader("foo", "bar").build();
|
||||
ScriptVariableGenerator scriptVariableGenerator =
|
||||
ScriptVariableGenerator scriptVariableGenerator =
|
||||
new DefaultScriptVariableGenerator(Collections.singletonMap("spam",(Object)"bucket"));
|
||||
MessageProcessor<Object> processor = new GroovyCommandMessageProcessor(scriptVariableGenerator);
|
||||
Object result = processor.processMessage(message);
|
||||
assertEquals("spam is bucket foo is bar", result.toString());
|
||||
}
|
||||
|
||||
@Test //INT-2567
|
||||
public void testBindingOverwrite() throws Exception {
|
||||
Binding binding = new Binding() {
|
||||
@Override
|
||||
public Object getVariable(String name) {
|
||||
throw new RuntimeException("intentional");
|
||||
}
|
||||
};
|
||||
Message<?> message = MessageBuilder.withPayload("foo").build();
|
||||
processor = new GroovyCommandMessageProcessor(binding);
|
||||
try {
|
||||
processor.processMessage(message);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (Exception e) {
|
||||
Assert.assertEquals("intentional", e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test //INT-2567
|
||||
public void testBindingOverwriteWithContext() throws Exception {
|
||||
final String defaultValue = "default";
|
||||
Binding binding = new Binding() {
|
||||
@Override
|
||||
public Object getVariable(String name) {
|
||||
try {
|
||||
return super.getVariable(name);
|
||||
}
|
||||
catch (MissingPropertyException e) {
|
||||
// ignore
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
ScriptVariableGenerator scriptVariableGenerator =
|
||||
new DefaultScriptVariableGenerator(Collections.singletonMap("spam",(Object)"bucket"));
|
||||
Message<?> message = MessageBuilder.withPayload("\"spam is $spam, foo is $foo\"").build();
|
||||
processor = new GroovyCommandMessageProcessor(binding, scriptVariableGenerator);
|
||||
Object result = processor.processMessage(message);
|
||||
assertEquals("spam is bucket, foo is default", result.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,13 +11,31 @@
|
||||
<queue/>
|
||||
</channel>
|
||||
|
||||
<beans:bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
|
||||
<beans:property name="scopes">
|
||||
<beans:map>
|
||||
<beans:entry key="thread" value="org.springframework.context.support.SimpleThreadScope"/>
|
||||
<beans:entry key="request" value="org.springframework.web.context.request.RequestScope"/>
|
||||
</beans:map>
|
||||
</beans:property>
|
||||
</beans:bean>
|
||||
|
||||
<groovy:control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="true"
|
||||
customizer="groovyCustomizer"/>
|
||||
|
||||
<beans:bean id="service" class="org.springframework.integration.groovy.config.GroovyControlBusTests$Service" />
|
||||
|
||||
<beans:bean id="groovyCustomizer"
|
||||
class="org.springframework.integration.groovy.config.GroovyControlBusTests.MyGroovyCustomizer"/>
|
||||
|
||||
|
||||
<beans:bean id="threadScopedService"
|
||||
scope="thread"
|
||||
class="org.springframework.integration.groovy.config.GroovyControlBusTests$Service" />
|
||||
|
||||
<beans:bean id="requestScopedService"
|
||||
scope="request"
|
||||
class="org.springframework.integration.groovy.config.GroovyControlBusTests$Service" />
|
||||
|
||||
<beans:bean id="nonManagedService" class="org.springframework.integration.groovy.config.GroovyControlBusTests$NonManagedService" />
|
||||
|
||||
<beans:bean id="groovyCustomizer"
|
||||
class="org.springframework.integration.groovy.config.GroovyControlBusTests$MyGroovyCustomizer"/>
|
||||
|
||||
</beans:beans>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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,13 +19,18 @@ package org.springframework.integration.groovy.config;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import groovy.lang.GroovyObject;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanCreationNotAllowedException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessageHandlingException;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
@@ -33,9 +38,15 @@ import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@@ -47,7 +58,7 @@ public class GroovyControlBusTests {
|
||||
|
||||
@Autowired
|
||||
private PollableChannel output;
|
||||
|
||||
|
||||
@Autowired
|
||||
private MyGroovyCustomizer groovyCustomizer;
|
||||
|
||||
@@ -61,6 +72,49 @@ public class GroovyControlBusTests {
|
||||
assertTrue(this.groovyCustomizer.executed);
|
||||
}
|
||||
|
||||
@Test //INT-2567
|
||||
public void testOperationWithCustomScope() {
|
||||
Message<?> message = MessageBuilder.withPayload("def result = threadScopedService.convert('testString')").build();
|
||||
this.input.send(message);
|
||||
assertEquals("cat", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
@Test //INT-2567
|
||||
public void testFailOperationWithCustomScope() {
|
||||
try {
|
||||
Message<?> message = MessageBuilder.withPayload("def result = requestScopedService.convert('testString')").build();
|
||||
this.input.send(message);
|
||||
fail("Expected BeanCreationException");
|
||||
}
|
||||
catch (Exception e) {
|
||||
Throwable cause = e.getCause();
|
||||
assertTrue("Expected BeanCreationException, got " + cause.getClass() + ":" + cause.getMessage(), cause instanceof BeanCreationException);
|
||||
assertTrue(cause.getMessage().contains("requestScopedService"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test //INT-2567
|
||||
public void testOperationWithRequestCustomScope() {
|
||||
RequestContextHolder.setRequestAttributes(new MockRequestAttributes());
|
||||
Message<?> message = MessageBuilder.withPayload("def result = requestScopedService.convert('testString')").build();
|
||||
this.input.send(message);
|
||||
assertEquals("cat", output.receive(0).getPayload());
|
||||
}
|
||||
|
||||
@Test //INT-2567
|
||||
public void testFailOperationOnNonManagedComponent() {
|
||||
try {
|
||||
Message<?> message = MessageBuilder.withPayload("def result = nonManagedService.convert('testString')").build();
|
||||
this.input.send(message);
|
||||
fail("Expected BeanCreationNotAllowedException");
|
||||
}
|
||||
catch (MessageHandlingException e) {
|
||||
Throwable cause = e.getCause();
|
||||
assertTrue("Expected BeanCreationNotAllowedException, got " + cause.getClass() + ":" + cause.getMessage(), cause instanceof BeanCreationNotAllowedException);
|
||||
assertTrue(cause.getMessage().contains("nonManagedService"));
|
||||
}
|
||||
}
|
||||
|
||||
@ManagedResource
|
||||
public static class Service {
|
||||
|
||||
@@ -69,14 +123,59 @@ public class GroovyControlBusTests {
|
||||
return "cat";
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyGroovyCustomizer implements GroovyObjectCustomizer{
|
||||
|
||||
|
||||
public static class NonManagedService {
|
||||
|
||||
public String convert(String input) {
|
||||
return "cat";
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyGroovyCustomizer implements GroovyObjectCustomizer {
|
||||
private volatile boolean executed;
|
||||
|
||||
|
||||
public void customize(GroovyObject goo) {
|
||||
this.executed = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static class MockRequestAttributes implements RequestAttributes {
|
||||
|
||||
private Map<String, Object> fakeRequest = new HashMap<String, Object>();
|
||||
|
||||
public Object getAttribute(String name, int scope) {
|
||||
return fakeRequest.get(name);
|
||||
}
|
||||
|
||||
public void setAttribute(String name, Object value, int scope) {
|
||||
fakeRequest.put(name, value);
|
||||
}
|
||||
|
||||
public void removeAttribute(String name, int scope) {
|
||||
}
|
||||
|
||||
public String[] getAttributeNames(int scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerDestructionCallback(String name, Runnable callback, int scope) {
|
||||
|
||||
}
|
||||
|
||||
public Object resolveReference(String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getSessionMutex() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
|
||||
<section id="groovy-config">
|
||||
<title>Groovy configuration</title>
|
||||
<para>
|
||||
<para>
|
||||
With Spring Integration 2.1, Groovy Support's configuration namespace
|
||||
is an extension of Spring Integration's Scripting Support and shares the core configuration
|
||||
and behavior described in detail in the
|
||||
is an extension of Spring Integration's Scripting Support and shares the core configuration
|
||||
and behavior described in detail in the
|
||||
<xref linkend="scripting">Scripting Support</xref> section. Even though Groovy scripts are
|
||||
well supported by generic Scripting Support, Groovy Support provides the
|
||||
<emphasis>Groovy</emphasis> configuration namespace which is backed by the Spring Framework's
|
||||
<classname>org.springframework.scripting.groovy.GroovyScriptFactory</classname> and related components,
|
||||
well supported by generic Scripting Support, Groovy Support provides the
|
||||
<emphasis>Groovy</emphasis> configuration namespace which is backed by the Spring Framework's
|
||||
<classname>org.springframework.scripting.groovy.GroovyScriptFactory</classname> and related components,
|
||||
offering extended capabilities for using Groovy. Below are a couple of sample configurations:</para>
|
||||
<para><emphasis>Filter</emphasis> <programlisting language="xml"><int:filter input-channel="referencedScriptInput">
|
||||
<int-groovy:script location="some/path/to/groovy/file/GroovyFilterTests.groovy"/>
|
||||
@@ -35,20 +35,20 @@
|
||||
return payload == 'good'
|
||||
]]></int-groovy:script>
|
||||
</int:filter></programlisting>
|
||||
As the above examples show, the configuration looks identical to the general Scripting Support configuration. The only
|
||||
difference is the use of the Groovy namespace as indicated in the examples by the <emphasis>int-groovy</emphasis> namespace prefix.
|
||||
Also note that the <code>lang</code> attribute on the <code><script></code> tag is not valid in this namespace.
|
||||
As the above examples show, the configuration looks identical to the general Scripting Support configuration. The only
|
||||
difference is the use of the Groovy namespace as indicated in the examples by the <emphasis>int-groovy</emphasis> namespace prefix.
|
||||
Also note that the <code>lang</code> attribute on the <code><script></code> tag is not valid in this namespace.
|
||||
</para>
|
||||
|
||||
|
||||
|
||||
<para><emphasis>Groovy object customization</emphasis> </para>
|
||||
|
||||
|
||||
<para>
|
||||
If you need to customize the Groovy object itself, beyond setting variables, you can reference
|
||||
a bean that implements <classname>org.springframework.scripting.groovy.GroovyObjectCustomizer</classname> via the
|
||||
<code>customizer</code> attribute. For example, this might be useful if you want to implement a domain-specific
|
||||
language (DSL) by modifying the MetaClass and registering functions to be available within the script:
|
||||
|
||||
|
||||
<programlisting language="xml"><![CDATA[<int:service-activator input-channel="groovyChannel">
|
||||
<int-groovy:script location="foo/SomeScript.groovy" customizer="groovyCustomizer"/>
|
||||
</int:service-activator>
|
||||
@@ -78,28 +78,28 @@
|
||||
<para>The Groovy Control Bus executes messages on the input channel as
|
||||
Groovy scripts. It takes a message, compiles the body to a Script,
|
||||
customizes it with a <classname>GroovyObjectCustomizer</classname>, and then executes it. The
|
||||
Control Bus' customizer exposes all the beans in the application context
|
||||
Control Bus' <classname>MessageProcessor</classname> exposes all beans in the application context
|
||||
that are annotated with <code>@ManagedResource</code>, implement Spring's
|
||||
<classname>Lifecycle</classname> interface or extend Spring's <classname>CustomizableThreadCreator</classname> base class
|
||||
(e.g. several of the <classname>TaskExecutor</classname> and <classname>TaskScheduler</classname> implementations).</para>
|
||||
<para>
|
||||
<important>
|
||||
Be careful about using managed beans with custom scopes (e.g. 'request') in the Control Bus' command scripts, especially
|
||||
inside an <emphasis>async</emphasis> message flow. If <emphasis>The Control Bus' customizer</emphasis> can't expose a bean
|
||||
from the application context, it skips that bean. For example, if a custom scope's context is not established, the attempt
|
||||
to get a bean within that scope will trigger a <classname>BeanCreationException</classname>. In that case, such a bean
|
||||
will be ignored, and the customizer will continue with other beans.
|
||||
inside an <emphasis>async</emphasis> message flow. If The Control Bus' <classname>MessageProcessor</classname>
|
||||
can't expose a bean from the application context, you may end up with some <classname>BeansException</classname>
|
||||
during <emphasis>command script's</emphasis> executing. For example, if a custom scope's context is not established,
|
||||
the attempt to get a bean within that scope will trigger a <classname>BeanCreationException</classname>.
|
||||
</important>
|
||||
</para>
|
||||
<para>
|
||||
If you need to further customize the Groovy objects, you can also provide a reference to a bean
|
||||
that implements <classname>org.springframework.scripting.groovy.GroovyObjectCustomizer</classname> via
|
||||
the <code>customizer</code> attribute.
|
||||
|
||||
|
||||
<programlisting language="xml"><![CDATA[<int-groovy:control-bus input-channel="input"
|
||||
output-channel="output"
|
||||
customizer="groovyCustomizer"/>
|
||||
|
||||
|
||||
<beans:bean id="groovyCustomizer" class="org.foo.MyGroovyObjectCustomizer"/>]]></programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user