From 74b4638eb951939ca8a452c17425a51ab374026c Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Sun, 13 Nov 2011 16:31:50 +0200 Subject: [PATCH] INT-2567: Improvement & refactor Groovy component Introduce VariableResolver, BeanFilter, FilteredBeanFactoryDecorator, GroovyVariableResolverBinding, refactoring ManagedBeansScriptVariableGenerator for usage GroovyVariableResolverBinding. GroovyVariableResolverBindingTests. additional tests for GroovyControlBus. add 'spring-web' dependency for tests about 'request' custom scope. Manual: change description about 'Groovy Control Bus' scripts limitation'. INT-2567: Improve & refacror Groovy Control Bus Introduce separate BindingOverwriteGroovyObjectCustomizerDecorator Internal GroovyControlBusFactoryBean$ManagedBeansBinding with delegation to provided BeanFactory Inline ScriptVariableGenerator implementation just for Message 'headers' variable Tests for new GroovyScriptPayloadMessageProcessor logic about binding Integration tests for managed beans in the custom scope Reference Manual: polishing description about Control Bus' behavior INT-2567 polishing added @link to javadocs --- build.gradle | 1 + ...rwriteGroovyObjectCustomizerDecorator.java | 42 +++++++ .../groovy/GroovyCommandMessageProcessor.java | 48 ++++++-- ...indingGroovyObjectCustomizerDecorator.java | 8 +- .../config/GroovyControlBusFactoryBean.java | 83 ++++++++----- .../groovy/config/GroovyControlBusParser.java | 11 +- .../groovy/config/GroovyScriptParser.java | 19 +-- ...ovyScriptPayloadMessageProcessorTests.java | 58 ++++++++- .../config/GroovyControlBusTests-context.xml | 26 +++- .../groovy/config/GroovyControlBusTests.java | 111 +++++++++++++++++- src/reference/docbook/groovy.xml | 38 +++--- 11 files changed, 355 insertions(+), 90 deletions(-) create mode 100644 spring-integration-groovy/src/main/java/org/springframework/integration/groovy/BindingOverwriteGroovyObjectCustomizerDecorator.java diff --git a/build.gradle b/build.gradle index 9dc3924882..5b690c1b4e 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/BindingOverwriteGroovyObjectCustomizerDecorator.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/BindingOverwriteGroovyObjectCustomizerDecorator.java new file mode 100644 index 0000000000..9fb5f3e01e --- /dev/null +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/BindingOverwriteGroovyObjectCustomizerDecorator.java @@ -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); + } + +} diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java index fd160c8a85..4869be62d0 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java @@ -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 { 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 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("-", ""); } + } diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/VariableBindingGroovyObjectCustomizerDecorator.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/VariableBindingGroovyObjectCustomizerDecorator.java index 10ec2075b6..f437b8245f 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/VariableBindingGroovyObjectCustomizerDecorator.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/VariableBindingGroovyObjectCustomizerDecorator.java @@ -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. 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 87b1b26566..8976dd7d56 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 @@ -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 { @@ -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 generateScriptVariables(Message message) { + Map variables = new HashMap(); + 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 beanFactory + * 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 generateScriptVariables(Message message) { - Map variables = new HashMap(); - 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 beanFactory. + } + 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."); } + } } 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 56d19a592f..8d66ad2117 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 @@ -1,11 +1,11 @@ /* - * Copyright 2002-2011 the original author or authors. - * + * Copyright 2002-2012 the original author or authors. + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. @@ -21,7 +21,10 @@ import org.springframework.integration.config.xml.AbstractConsumerEndpointParser import org.springframework.integration.config.xml.IntegrationNamespaceUtils; /** + * Parser for the <groovy:control-bus/> element. + * * @author Dave Syer + * @author Artem Bilan * @since 2.0 */ public class GroovyControlBusParser extends AbstractConsumerEndpointParser { diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java index ed263b9fdf..5ead6352db 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/config/GroovyScriptParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,24 +16,29 @@ package org.springframework.integration.groovy.config; +import groovy.lang.Script; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.groovy.GroovyScriptExecutingMessageProcessor; import org.springframework.integration.scripting.config.AbstractScriptParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.w3c.dom.Element; /** + * Parser for the <groovy:script/> element. + * * @author Mark Fisher * @author Oleg Zhurakousky * @author David Turanski + * @author Artem Bilan * @since 2.0 */ public class GroovyScriptParser extends AbstractScriptParser { - + @Override protected String getBeanClassName(Element element) { - return "org.springframework.integration.groovy.GroovyScriptExecutingMessageProcessor"; + return GroovyScriptExecutingMessageProcessor.class.getName(); } /* (non-Javadoc) @@ -41,14 +46,14 @@ public class GroovyScriptParser extends AbstractScriptParser { */ @Override protected String getScriptSourceClassName() { - return "groovy.lang.Script"; + return Script.class.getName(); } - + protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "customizer"); } - - + + } 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 9727913557..6b1bc9b292 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 @@ -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 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()); + } + } diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml index ee1dce0632..8dd231009b 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml @@ -11,13 +11,31 @@ + + + + + + + + + - - - + + + + + + + + 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 616cc868dd..e894a14958 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 @@ -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 fakeRequest = new HashMap(); + + 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; + } + } } diff --git a/src/reference/docbook/groovy.xml b/src/reference/docbook/groovy.xml index 054733d82a..7a9adbefb4 100644 --- a/src/reference/docbook/groovy.xml +++ b/src/reference/docbook/groovy.xml @@ -17,14 +17,14 @@
Groovy configuration - + 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 Scripting Support section. Even though Groovy scripts are - well supported by generic Scripting Support, Groovy Support provides the - Groovy configuration namespace which is backed by the Spring Framework's - org.springframework.scripting.groovy.GroovyScriptFactory and related components, + well supported by generic Scripting Support, Groovy Support provides the + Groovy configuration namespace which is backed by the Spring Framework's + org.springframework.scripting.groovy.GroovyScriptFactory and related components, offering extended capabilities for using Groovy. Below are a couple of sample configurations: Filter <int:filter input-channel="referencedScriptInput"> <int-groovy:script location="some/path/to/groovy/file/GroovyFilterTests.groovy"/> @@ -35,20 +35,20 @@ return payload == 'good' ]]></int-groovy:script> </int:filter> - 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 int-groovy namespace prefix. - Also note that the lang attribute on the <script> 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 int-groovy namespace prefix. + Also note that the lang attribute on the <script> tag is not valid in this namespace. - + Groovy object customization - + If you need to customize the Groovy object itself, beyond setting variables, you can reference a bean that implements org.springframework.scripting.groovy.GroovyObjectCustomizer via the customizer 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: - + @@ -78,28 +78,28 @@ 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 - Control Bus' customizer exposes all the beans in the application context + Control Bus' MessageProcessor exposes all 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). Be careful about using managed beans with custom scopes (e.g. 'request') in the Control Bus' command scripts, especially - inside an async message flow. If The Control Bus' customizer 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 BeanCreationException. In that case, such a bean - will be ignored, and the customizer will continue with other beans. + inside an async message flow. If The Control Bus' MessageProcessor + can't expose a bean from the application context, you may end up with some BeansException + during command script's executing. For example, if a custom scope's context is not established, + the attempt to get a bean within that scope will trigger a BeanCreationException. If you need to further customize the Groovy objects, you can also provide a reference to a bean that implements org.springframework.scripting.groovy.GroovyObjectCustomizer via the customizer attribute. - + - + ]]>