diff --git a/docs/src/reference/docbook/groovy.xml b/docs/src/reference/docbook/groovy.xml index 93b78d872b..70cf390122 100644 --- a/docs/src/reference/docbook/groovy.xml +++ b/docs/src/reference/docbook/groovy.xml @@ -8,10 +8,10 @@ Groovy support 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 project website @@ -20,13 +20,14 @@ 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 GroovyScriptExecutingMessageProcessor which will - create a groovy Binding object identifying Message Payload as - payload variable and Message Headers as headers - variable. All that is left for you to do is write script that uses these - variables. Below are couple of sample configurations: + bind the Message Payload as a + payload variable and the Message Headers as a headers + 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: Filter <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> You see that script could be included inline - or via location attribute using the groovy namespace - sport.  +</filter> + + Here, you see that the script can be included inline + or via the location attribute using the groovy namespace + support. Other supported elements are router, service-activator, - transformer, splitter + transformer, and splitter. The configuration would look identical to that + above other than the main element's name. + + 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 refresh-check-delay attribute on the script + element. + + <groovy:script location="..." refresh-check-delay="5000"/> + + 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. + + <groovy:script location="..." refresh-check-delay="0"/> + + 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). + + <groovy:script location="..." refresh-check-delay="-1"/> - 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 - refresh-check-delay attribute on script - element. The reason for this attribute is to make reloading of the script - more efficient.  <groovy:script location="..." refresh-check-delay="5000"/> - 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. <groovy:script location="..." refresh-check-delay="0"/> - 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. <groovy:script location="..." refresh-check-delay="-1"/> Any negative number value means the script will never be refreshed after - initial initialization of application context. DEFAULT BEHAVIOR - Inline defined script can not be reloaded. + 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. + Inline defined scripts can not be reloaded.
@@ -80,7 +94,9 @@ 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. + 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).
diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/BeanFactoryContextBindingCustomizer.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/BeanFactoryContextBindingCustomizer.java deleted file mode 100644 index 3486fe963c..0000000000 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/BeanFactoryContextBindingCustomizer.java +++ /dev/null @@ -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)); - } - } - } - -} diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptPayloadMessageProcessor.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptPayloadMessageProcessor.java deleted file mode 100644 index 8c3830897a..0000000000 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptPayloadMessageProcessor.java +++ /dev/null @@ -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 { - - private final GroovyObjectCustomizer customizer; - - - public GroovyScriptPayloadMessageProcessor() { - this((GroovyObjectCustomizer) null); - } - - public GroovyScriptPayloadMessageProcessor(Map 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("-", ""); - } - -} diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java index 78373c1f51..e02d1426ca 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusFactoryBean.java @@ -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); + } + } + } + } + } + } diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusParser.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusParser.java index 1ba9573408..26e61c9909 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusParser.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyControlBusParser.java @@ -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(); - } - } diff --git a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd index 5e3c4de53f..0f6103ecb8 100644 --- a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd +++ b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.0.xsd @@ -41,31 +41,17 @@ - + + String payload in incoming messages. The variable bindings will include any @ManagedResource, Lifecycle, + or CustomizableThreadCreator instances from within the ApplicationContext. + ]]> - - - - 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. - - - - - - - - diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptPayloadMessageProcessorTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptPayloadMessageProcessorTests.java index d59a5f14bf..5f0258ba19 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptPayloadMessageProcessorTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptPayloadMessageProcessorTests.java @@ -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 processor = new GroovyScriptPayloadMessageProcessor(Collections.singletonMap("spam", + MessageProcessor processor = new GroovyCommandMessageProcessor(Collections.singletonMap("spam", "bucket")); Object result = processor.processMessage(message); assertEquals("spam is bucket foo is bar", result.toString()); diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java index b625ea1ac0..9b164bc7a9 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java @@ -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"; } } + }