INT-1552 doc polishing, also updated Groovy-based Control Bus to be more consistent with the SpEL version

This commit is contained in:
Mark Fisher
2010-11-22 14:01:39 -05:00
parent 36e43a8ed1
commit 4e8076fd2b
8 changed files with 103 additions and 211 deletions

View File

@@ -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">&lt;filter input-channel="referencedScriptInput"&gt;
&lt;groovy:script location="some/path/to/groovy/file/GroovyFilterTests.groovy"/&gt;
@@ -36,30 +37,43 @@
&lt;groovy:script&gt;&lt;![CDATA[
return payload == 'good'
]]&gt;&lt;/groovy:script&gt;
&lt;/filter&gt;</programlisting> You see that script could be included inline
or via <code>location</code> attribute using the groovy namespace
sport. </para>
&lt;/filter&gt;</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">&lt;groovy:script location="..." refresh-check-delay="5000"/&gt;</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">&lt;groovy:script location="..." refresh-check-delay="0"/&gt;</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">&lt;groovy:script location="..." refresh-check-delay="-1"/&gt;</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">&lt;groovy:script location="..." refresh-check-delay="5000"/&gt;</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">&lt;groovy:script location="..." refresh-check-delay="0"/&gt;</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">&lt;groovy:script location="..." refresh-check-delay="-1"/&gt;</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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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