INT-1552 doc polishing, also updated Groovy-based Control Bus to be more consistent with the SpEL version
This commit is contained in:
@@ -8,10 +8,10 @@
|
||||
<title>Groovy support</title>
|
||||
|
||||
<para>With Spring Integration 2.0 we've added Groovy support allowing you to
|
||||
use Groovy scripting language to provide integration and business logic for
|
||||
various integration components similar to the way Spring Expression Language
|
||||
(SpEL) is use to implement routing, transformation and other integration
|
||||
concerns. For more information about Groovy please refer to Groovy
|
||||
use the Groovy scripting language to provide the logic for
|
||||
various integration components similar to the way the Spring Expression Language
|
||||
(SpEL) is supported for routing, transformation and other integration
|
||||
concerns. For more information about Groovy please refer to the Groovy
|
||||
documentation which you can find
|
||||
on the <ulink url="http://groovy.codehaus.org">project website</ulink></para>
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
|
||||
<para>Depending on the complexity of your integration requirements Groovy
|
||||
scripts could be provided inline as CDATA in XML configuration or as a
|
||||
reference to a file containing Groovy script. To enable Groovy support
|
||||
Spring Integration defines
|
||||
reference to a file containing the Groovy script. To enable Groovy support
|
||||
Spring Integration defines a
|
||||
<classname>GroovyScriptExecutingMessageProcessor</classname> which will
|
||||
create a groovy Binding object identifying Message Payload as
|
||||
<code>payload</code> variable and Message Headers as <code>headers</code>
|
||||
variable. All that is left for you to do is write script that uses these
|
||||
variables. Below are couple of sample configurations:</para>
|
||||
bind the Message Payload as a
|
||||
<code>payload</code> variable and the Message Headers as a <code>headers</code>
|
||||
variable within the script execution context. All that is left for you to do is
|
||||
write a script that uses those
|
||||
variables. Below are a couple of sample configurations:</para>
|
||||
|
||||
<para><emphasis>Filter</emphasis> <programlisting language="xml"><filter input-channel="referencedScriptInput">
|
||||
<groovy:script location="some/path/to/groovy/file/GroovyFilterTests.groovy"/>
|
||||
@@ -36,30 +37,43 @@
|
||||
<groovy:script><![CDATA[
|
||||
return payload == 'good'
|
||||
]]></groovy:script>
|
||||
</filter></programlisting> You see that script could be included inline
|
||||
or via <code>location</code> attribute using the groovy namespace
|
||||
sport. </para>
|
||||
</filter></programlisting>
|
||||
|
||||
Here, you see that the script can be included inline
|
||||
or via the <code>location</code> attribute using the groovy namespace
|
||||
support.</para>
|
||||
|
||||
<para>Other supported elements are <emphasis>router, service-activator,
|
||||
transformer, splitter</emphasis></para>
|
||||
transformer, and splitter. The configuration would look identical to that
|
||||
above other than the main element's name.</emphasis></para>
|
||||
|
||||
<para>Another interesting aspect of using Groovy support is the framework's
|
||||
ability to update (reload) scripts without restarting the Application
|
||||
Context. To accomplish this, all you need to do is specify
|
||||
the <code>refresh-check-delay</code> attribute on the <emphasis>script</emphasis>
|
||||
element.
|
||||
|
||||
<programlisting language="xml"><groovy:script location="..." refresh-check-delay="5000"/></programlisting>
|
||||
|
||||
In the above example any invocations that occur within the 5 seconds immediately following the
|
||||
updating of the script would still be using the old script. However, any invocation that occurs
|
||||
after those 5 seconds have elapsed will
|
||||
result in execution of the new script. This is a good example where 'near real
|
||||
time' is acceptable.
|
||||
|
||||
<programlisting language="xml"><groovy:script location="..." refresh-check-delay="0"/></programlisting>
|
||||
|
||||
In the above example the context will be updated with any script modifications
|
||||
as soon as such modification occurs. Basically this is an example of
|
||||
'real-time' configuration and might not be the most efficient option (but could be useful during development).
|
||||
|
||||
<programlisting language="xml"><groovy:script location="..." refresh-check-delay="-1"/></programlisting>
|
||||
|
||||
<para>Another interesting aspect of using Groovy support is framework's
|
||||
ability to update (reload) scripts without restarting the Application
|
||||
Context. To accomplish this all you need is specify
|
||||
<code>refresh-check-delay</code> attribute on <emphasis>script</emphasis>
|
||||
element. The reason for this attribute is to make reloading of the script
|
||||
more efficient. <programlisting language="xml"><groovy:script location="..." refresh-check-delay="5000"/></programlisting>
|
||||
In the above example for the next 5 seconds after you update the script
|
||||
you'll still be using the old script and after 5 seconds the context will
|
||||
be updated with the new script. This is a good example where 'near real
|
||||
time' is acceptable. <programlisting language="xml"><groovy:script location="..." refresh-check-delay="0"/></programlisting>
|
||||
In the above example the context will be updated with the new script every
|
||||
time the script is modified. Basically this is the example of the
|
||||
'real-time' and might not be the most efficient way. <programlisting
|
||||
language="xml"><groovy:script location="..." refresh-check-delay="-1"/></programlisting>
|
||||
Any negative number value means the script will never be refreshed after
|
||||
initial initialization of application context. DEFAULT BEHAVIOR
|
||||
<important>Inline defined script can not be reloaded.</important></para>
|
||||
initial initialization of the application context. This is the default behavior.
|
||||
In this case, the "dynamic" aspect of Groovy is not being used, but the syntax
|
||||
might be the primary reason that Groovy has been chosen in the first place.
|
||||
<important>Inline defined scripts can not be reloaded.</important></para>
|
||||
</section>
|
||||
|
||||
<section id="groovy-control-bus">
|
||||
@@ -80,7 +94,9 @@
|
||||
<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 GroovyObjectCustomizer, and then executes it. The
|
||||
default customizer just exposes all the beans in the application context
|
||||
as script context objects.</para>
|
||||
Control Bus' customizer exposes all the beans in the application context
|
||||
that are annotated with @ManagedResource, implement Spring's
|
||||
Lifecycle interface or extend Spring's CustomizableThreadCreator base class
|
||||
(e.g. several of the TaskExecutor and TaskScheduler implementations).</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @since 2.0
|
||||
*/
|
||||
public class BeanFactoryContextBindingCustomizer implements GroovyObjectCustomizer, BeanFactoryAware {
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
|
||||
public BeanFactoryContextBindingCustomizer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public BeanFactoryContextBindingCustomizer(BeanFactory beanFactory) {
|
||||
setBeanFactory(beanFactory);
|
||||
}
|
||||
|
||||
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.beanFactory = (beanFactory instanceof ListableBeanFactory) ? (ListableBeanFactory) beanFactory : null;
|
||||
}
|
||||
|
||||
public void customize(GroovyObject goo) {
|
||||
if (this.beanFactory != null) {
|
||||
Binding binding = ((Script) goo).getBinding();
|
||||
for (String name : this.beanFactory.getBeanDefinitionNames()) {
|
||||
binding.setVariable(name, this.beanFactory.getBean(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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.GString;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.handler.AbstractScriptExecutingMessageProcessor;
|
||||
import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
|
||||
import org.springframework.scripting.groovy.GroovyScriptFactory;
|
||||
import org.springframework.scripting.support.StaticScriptSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Mark Fisher
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GroovyScriptPayloadMessageProcessor extends AbstractScriptExecutingMessageProcessor<Object> {
|
||||
|
||||
private final GroovyObjectCustomizer customizer;
|
||||
|
||||
|
||||
public GroovyScriptPayloadMessageProcessor() {
|
||||
this((GroovyObjectCustomizer) null);
|
||||
}
|
||||
|
||||
public GroovyScriptPayloadMessageProcessor(Map<String, ?> map) {
|
||||
this(new MapContextBindingCustomizer(map));
|
||||
}
|
||||
|
||||
public GroovyScriptPayloadMessageProcessor(GroovyObjectCustomizer customizer) {
|
||||
this.customizer = customizer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ScriptSource getScriptSource(Message<?> message) {
|
||||
Object payload = message.getPayload();
|
||||
Assert.isInstanceOf(String.class, payload, "Payload must be a String containing a Groovy script.");
|
||||
String className = generateScriptName(message);
|
||||
return new StaticScriptSource((String) payload, className);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object executeScript(ScriptSource scriptSource, Message<?> message) throws Exception {
|
||||
// Keeping everything local prevents PermGen (class instances) leaks...
|
||||
MessageContextBindingCustomizer bindingCustomizer = new MessageContextBindingCustomizer(this.customizer);
|
||||
bindingCustomizer.setMessage(message);
|
||||
GroovyScriptFactory scriptFactory = new GroovyScriptFactory(this.getClass().getSimpleName(), bindingCustomizer);
|
||||
Object result = scriptFactory.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("-", "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,10 +13,21 @@
|
||||
|
||||
package org.springframework.integration.groovy.config;
|
||||
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.GroovyObject;
|
||||
import groovy.lang.Script;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.groovy.GroovyScriptPayloadMessageProcessor;
|
||||
import org.springframework.integration.groovy.GroovyCommandMessageProcessor;
|
||||
import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
|
||||
import org.springframework.util.CustomizableThreadCreator;
|
||||
|
||||
/**
|
||||
* FactoryBean for creating {@link MessageHandler} instances to handle a message as a Groovy Script.
|
||||
@@ -28,13 +39,6 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac
|
||||
|
||||
private volatile Long sendTimeout;
|
||||
|
||||
private final GroovyScriptPayloadMessageProcessor processor;
|
||||
|
||||
|
||||
public GroovyControlBusFactoryBean(GroovyScriptPayloadMessageProcessor processor) {
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
|
||||
public void setSendTimeout(Long sendTimeout) {
|
||||
this.sendTimeout = sendTimeout;
|
||||
@@ -42,7 +46,9 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler() {
|
||||
return this.configureHandler(new ServiceActivatingHandler(this.processor));
|
||||
ControlBusContextBindingCustomizer customizer = new ControlBusContextBindingCustomizer(this.getBeanFactory());
|
||||
GroovyCommandMessageProcessor processor = new GroovyCommandMessageProcessor(customizer);
|
||||
return this.configureHandler(new ServiceActivatingHandler(processor));
|
||||
}
|
||||
|
||||
private ServiceActivatingHandler configureHandler(ServiceActivatingHandler handler) {
|
||||
@@ -52,4 +58,31 @@ public class GroovyControlBusFactoryBean extends AbstractSimpleMessageHandlerFac
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds any @ManagedResource, Lifecycle, or CustomizableThreadCreator instances from
|
||||
* the application context to the Script variable bindings.
|
||||
*/
|
||||
private static class ControlBusContextBindingCustomizer implements GroovyObjectCustomizer {
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
public ControlBusContextBindingCustomizer(BeanFactory beanFactory) {
|
||||
this.beanFactory = (beanFactory instanceof ListableBeanFactory) ? (ListableBeanFactory) beanFactory : null;
|
||||
}
|
||||
|
||||
public void customize(GroovyObject goo) {
|
||||
if (this.beanFactory != null) {
|
||||
Binding binding = ((Script) goo).getBinding();
|
||||
for (String name : this.beanFactory.getBeanDefinitionNames()) {
|
||||
Object bean = this.beanFactory.getBean(name);
|
||||
if (bean instanceof Lifecycle || bean instanceof CustomizableThreadCreator
|
||||
|| (AnnotationUtils.findAnnotation(bean.getClass(), ManagedResource.class) != null)) {
|
||||
binding.setVariable(name, bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,13 +15,10 @@ package org.springframework.integration.groovy.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -29,29 +26,12 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class GroovyControlBusParser extends AbstractConsumerEndpointParser {
|
||||
|
||||
private static final String CUSTOMIZER_ATTRIBUTE = "customizer";
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GroovyControlBusFactoryBean.class);
|
||||
builder.addConstructorArgValue(getMessageProcessorBeanDefinition(element, parserContext));
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order");
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected BeanMetadataElement getMessageProcessorBeanDefinition(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
"org.springframework.integration.groovy.GroovyScriptPayloadMessageProcessor");
|
||||
String customizerAttr = element.getAttribute(CUSTOMIZER_ATTRIBUTE);
|
||||
if (StringUtils.hasText(customizerAttr)) {
|
||||
builder.addConstructorArgReference(customizerAttr.trim());
|
||||
}
|
||||
else {
|
||||
builder.addConstructorArgValue(new RootBeanDefinition(
|
||||
"org.springframework.integration.groovy.BeanFactoryContextBindingCustomizer"));
|
||||
}
|
||||
return builder.getBeanDefinition();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,31 +41,17 @@
|
||||
|
||||
<xsd:element name="control-bus">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Control bus that accepts messages in the form of Groovy scripts. The scripts should be provided as
|
||||
String payloads
|
||||
in incoming messages
|
||||
</xsd:documentation>
|
||||
String payload in incoming messages. The variable bindings will include any @ManagedResource, Lifecycle,
|
||||
or CustomizableThreadCreator instances from within the ApplicationContext.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:all minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="poller" type="integration:innerPollerType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:all>
|
||||
<xsd:attributeGroup ref="integration:inputOutputChannelGroup" />
|
||||
<xsd:attribute name="customizer" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
A reference to a static GroovyObjectCustomizer that will be used to modify the Groovy scripts
|
||||
sent to the control channel. By default a customizer is used that simply exposes all beans in the application
|
||||
context by name in the scripts.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:expected-type type="org.springframework.scripting.groovy.GroovyObjectCustomizer" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class GroovyScriptPayloadMessageProcessorTests {
|
||||
|
||||
private AtomicInteger countHolder = new AtomicInteger();
|
||||
|
||||
private GroovyScriptPayloadMessageProcessor processor = new GroovyScriptPayloadMessageProcessor();
|
||||
private GroovyCommandMessageProcessor processor = new GroovyCommandMessageProcessor();
|
||||
|
||||
@Test
|
||||
@Repeat(20)
|
||||
@@ -61,7 +61,7 @@ public class GroovyScriptPayloadMessageProcessorTests {
|
||||
public void testSimpleExecutionWithContext() throws Exception {
|
||||
Message<?> message = MessageBuilder.withPayload("\"spam is $spam foo is $headers.foo\"")
|
||||
.setHeader("foo", "bar").build();
|
||||
MessageProcessor<Object> processor = new GroovyScriptPayloadMessageProcessor(Collections.singletonMap("spam",
|
||||
MessageProcessor<Object> processor = new GroovyCommandMessageProcessor(Collections.singletonMap("spam",
|
||||
"bucket"));
|
||||
Object result = processor.processMessage(message);
|
||||
assertEquals("spam is bucket foo is bar", result.toString());
|
||||
|
||||
@@ -21,11 +21,14 @@ import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -51,9 +54,13 @@ public class GroovyControlBusTests {
|
||||
assertNull(output.receive(0));
|
||||
}
|
||||
|
||||
@ManagedResource
|
||||
public static class Service {
|
||||
|
||||
@ManagedOperation
|
||||
public String convert(String input) {
|
||||
return "cat";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user