Merge pull request #446 from artembilan/INT-2567
* INT-2567: INT-2567: Improvement & refactor Groovy component
This commit is contained in:
@@ -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