Merge pull request #446 from artembilan/INT-2567

* INT-2567:
  INT-2567: Improvement & refactor Groovy component
This commit is contained in:
Oleg Zhurakousky
2012-06-01 11:01:25 -04:00
11 changed files with 355 additions and 90 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 &lt;groovy:control-bus/&gt; element.
*
* @author Dave Syer
* @author Artem Bilan
* @since 2.0
*/
public class GroovyControlBusParser extends AbstractConsumerEndpointParser {

View File

@@ -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 &lt;groovy:script/&gt; 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");
}
}

View File

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

View File

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

View File

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

View File

@@ -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">&lt;int:filter input-channel="referencedScriptInput"&gt;
&lt;int-groovy:script location="some/path/to/groovy/file/GroovyFilterTests.groovy"/&gt;
@@ -35,20 +35,20 @@
return payload == 'good'
]]&gt;&lt;/int-groovy:script&gt;
&lt;/int:filter&gt;</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>&lt;script&gt;</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>&lt;script&gt;</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>