From f0c4bdb75633372572b5ab979566c7ed947783dc Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 7 Oct 2013 13:04:27 +0300 Subject: [PATCH 01/12] INT-3166: Improve Groovy Processor Performance Previously, there was a `synchronized` block in the `GroovyScriptExecutingMessageProcessor` to achieve thread-safety around script variables. In addition, the parsing and executing logic was delegated to `GroovyScriptFactory`, who, in turn, has its own `synchronized` around script parsing. This causes a bottleneck in a messaging architecture. * Introduce `GroovyScriptExecutor implements ScriptExecutor` to encapsulate Groovy parsing and executing logic * synchronize via `ReentrantLock` only when script resource `isModified()` JIRA: https://jira.springsource.org/browse/INT-3166 INT-3166: Fixes and Polishing INT-3166: add synchronized double check INT-3166: Remove `GroovyScriptExecutor` Polishing tests and logic around exceptions Fix up some white space issues in Groovy*Tests --- .../groovy/GroovyCommandMessageProcessor.java | 14 +- ...GroovyScriptExecutingMessageProcessor.java | 107 +++++++-- .../groovy/GroovyExpressionTests.java | 213 ------------------ ...yScriptExecutingMessageProcessorTests.java | 48 +++- .../groovy/config/GroovyFilterTests.java | 1 + .../groovy/config/GroovyRouterTests.java | 1 + .../config/GroovyServiceActivatorTests.java | 1 - .../groovy/config/GroovySplitterTests.java | 7 +- .../groovy/config/GroovyTransformerTests.java | 11 +- ...stractScriptExecutingMessageProcessor.java | 24 +- .../jsr223/Jsr223ServiceActivatorTests.java | 1 - .../jsr223/Jsr223ScriptExecutorTests.java | 52 ++--- .../jsr223/PythonScriptExecutorTests.java | 33 ++- 13 files changed, 197 insertions(+), 316 deletions(-) delete mode 100644 spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyExpressionTests.java 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 ce6196da45..08893a0bee 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 @@ -13,9 +13,6 @@ package org.springframework.integration.groovy; -import groovy.lang.Binding; -import groovy.lang.GString; - import java.util.Map; import org.springframework.integration.Message; @@ -29,6 +26,9 @@ import org.springframework.scripting.support.StaticScriptSource; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import groovy.lang.Binding; +import groovy.lang.GString; + /** * @author Dave Syer * @author Mark Fisher @@ -109,11 +109,11 @@ public class GroovyCommandMessageProcessor extends AbstractScriptExecutingMessag customizerDecorator.setVariables(variables); } GroovyScriptFactory factory = new GroovyScriptFactory(this.getClass().getSimpleName(), customizerDecorator); - if (getBeanClassLoader() != null) { - factory.setBeanClassLoader(getBeanClassLoader()); + if (this.beanClassLoader != null) { + factory.setBeanClassLoader(this.beanClassLoader); } - if (getBeanFactory() != null) { - factory.setBeanFactory(getBeanFactory()); + if (this.beanFactory != null) { + factory.setBeanFactory(this.beanFactory); } Object result = factory.getScriptedObject(scriptSource, null); return (result instanceof GString) ? result.toString() : result; diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java index eb79bc172e..72f33ace8e 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java @@ -16,40 +16,52 @@ package org.springframework.integration.groovy; -import groovy.lang.GString; - import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.integration.Message; import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor; import org.springframework.integration.scripting.ScriptVariableGenerator; +import org.springframework.scripting.ScriptCompilationException; import org.springframework.scripting.ScriptSource; import org.springframework.scripting.groovy.GroovyObjectCustomizer; -import org.springframework.scripting.groovy.GroovyScriptFactory; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; +import org.springframework.util.ClassUtils; + +import groovy.lang.Binding; +import groovy.lang.GString; +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyObject; +import groovy.lang.MetaClass; +import groovy.lang.Script; /** + * The {@link org.springframework.integration.handler.MessageProcessor} implementation + * to evaluate Groovy scripts. + * * @author Dave Syer * @author Mark Fisher * @author Oleg Zhurakousky * @author Stefan Reuter + * @author Artem Bilan * @since 2.0 */ -public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor implements InitializingBean { +public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor { - private final GroovyScriptFactory scriptFactory; + private final VariableBindingGroovyObjectCustomizerDecorator customizerDecorator = + new VariableBindingGroovyObjectCustomizerDecorator(); - private final VariableBindingGroovyObjectCustomizerDecorator - customizerDecorator = new VariableBindingGroovyObjectCustomizerDecorator(); + private final Lock scriptLock = new ReentrantLock(); private volatile ScriptSource scriptSource; + private volatile GroovyClassLoader groovyClassLoader = new GroovyClassLoader(ClassUtils.getDefaultClassLoader()); + + private volatile Class scriptClass; /** * Create a processor for the given {@link ScriptSource} that will use a @@ -58,7 +70,6 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti public GroovyScriptExecutingMessageProcessor(ScriptSource scriptSource) { super(); this.scriptSource = scriptSource; - this.scriptFactory = new GroovyScriptFactory(this.getClass().getSimpleName(), this.customizerDecorator); } /** @@ -68,9 +79,21 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti public GroovyScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptVariableGenerator scriptVariableGenerator) { super(scriptVariableGenerator); this.scriptSource = scriptSource; - this.scriptFactory = new GroovyScriptFactory(this.getClass().getSimpleName(), this.customizerDecorator); } + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + super.setBeanClassLoader(classLoader); + this.groovyClassLoader = new GroovyClassLoader(classLoader); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + super.setBeanFactory(beanFactory); + if (beanFactory instanceof ConfigurableListableBeanFactory) { + ((ConfigurableListableBeanFactory) beanFactory).ignoreDependencyType(MetaClass.class); + } + } /** * Sets a {@link GroovyObjectCustomizer} for this processor. @@ -87,22 +110,58 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti @Override protected Object executeScript(ScriptSource scriptSource, Map variables) throws Exception { Assert.notNull(scriptSource, "scriptSource must not be null"); - synchronized (this) { - if (!CollectionUtils.isEmpty(variables)) { - this.customizerDecorator.setVariables(variables); + this.parseScriptIfNecessary(scriptSource); + Object result = this.execute(variables); + return (result instanceof GString) ? result.toString() : result; + } + + + private void parseScriptIfNecessary(ScriptSource scriptSource) throws Exception { + if (this.scriptClass == null || scriptSource.isModified()) { + this.scriptLock.lockInterruptibly(); + try { + // synchronized double check + if (this.scriptClass == null || scriptSource.isModified()) { + this.scriptClass = this.groovyClassLoader.parseClass( + scriptSource.getScriptAsString(), scriptSource.suggestedClassName()); + } + } + finally { + this.scriptLock.unlock(); } - Object result = this.scriptFactory.getScriptedObject(scriptSource, null); - return (result instanceof GString) ? result.toString() : result; } } - @Override - public void afterPropertiesSet() throws Exception { - if (getBeanClassLoader() != null) { - this.scriptFactory.setBeanClassLoader(getBeanClassLoader()); + private Object execute(Map variables) throws ScriptCompilationException { + try { + GroovyObject goo = (GroovyObject) this.scriptClass.newInstance(); + + GroovyObjectCustomizer groovyObjectCustomizer = this.customizerDecorator; + if (variables != null) { + // Override empty Script.Binding with new one with 'variables' + groovyObjectCustomizer = new BindingOverwriteGroovyObjectCustomizerDecorator(new Binding(variables)); + ((VariableBindingGroovyObjectCustomizerDecorator) groovyObjectCustomizer).setCustomizer(this.customizerDecorator); + } + + if (goo instanceof Script) { + // Allow metaclass and other customization. + groovyObjectCustomizer.customize(goo); + // A Groovy script, probably creating an instance: let's execute it. + return ((Script) goo).run(); + } + else { + // An instance of the scripted class: let's return it as-is. + return goo; + } } - if (getBeanFactory() != null) { - this.scriptFactory.setBeanFactory(getBeanFactory()); + catch (InstantiationException ex) { + throw new ScriptCompilationException( + this.scriptSource, "Could not instantiate Groovy script class: " + this.scriptClass.getName(), ex); + } + catch (IllegalAccessException ex) { + throw new ScriptCompilationException( + this.scriptSource, "Could not access Groovy script constructor: " + this.scriptClass.getName(), ex); } } + } diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyExpressionTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyExpressionTests.java deleted file mode 100644 index db9f2abc65..0000000000 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyExpressionTests.java +++ /dev/null @@ -1,213 +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 static org.junit.Assert.assertEquals; - -import groovy.lang.GroovyObject; -import groovy.lang.Script; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.Executors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.log4j.Level; -import org.apache.log4j.LogManager; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.core.io.ByteArrayResource; -import org.springframework.scripting.groovy.GroovyObjectCustomizer; -import org.springframework.scripting.groovy.GroovyScriptFactory; -import org.springframework.scripting.support.ResourceScriptSource; -import org.springframework.util.Assert; - -/** - * @author Dave Syer - */ -public class GroovyExpressionTests { - - private static Log logger = LogFactory.getLog(GroovyExpressionTests.class); - - @Before - public void setLogLevel() { - LogManager.getLogger(getClass()).setLevel(Level.DEBUG); - } - - @After - public void resetLogLevel() { - LogManager.getLogger(getClass()).setLevel(Level.INFO); - } - - @Test - public void testScriptFactoryCustomizer() throws Exception { - Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo")); - GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer); - ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource("\"name=${name}\"".getBytes(), "InlineScript")); - Object scriptedObject = factory.getScriptedObject(scriptSource, null); - assertEquals("name=foo", scriptedObject.toString()); - customizer.setMap(Collections.singletonMap("name", (Object) "bar")); - scriptedObject = factory.getScriptedObject(scriptSource, null); - assertEquals("name=bar", scriptedObject.toString()); - } - - @Test - public void testScriptFactoryCustomizerThreadSafety() throws Exception { - final Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo")); - final GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer); - final ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource( - "\"name=${name}\"".getBytes(), "InlineScript")); - Object scriptedObject = factory.getScriptedObject(scriptSource, null); - assertEquals("name=foo", scriptedObject.toString()); - CompletionService completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(10)); - for (int i = 0; i < 100; i++) { - final String name = "bar" + i; - completionService.submit(new Callable() { - public String call() throws Exception { - Object scriptedObject; - synchronized (customizer) { - customizer.setMap(Collections.singletonMap("name", (Object) name)); - scriptedObject = factory.getScriptedObject(scriptSource, null); - } - String result = scriptedObject.toString(); - logger.debug("Result=" + result + " with name=" + name); - if (!("name=" + name).equals(result)) { - throw new IllegalStateException("Wrong value (" + result + ") for: " + name); - } - return name; - } - }); - } - Set set = new HashSet(); - for (int i = 0; i < 100; i++) { - set.add(completionService.take().get()); - } - assertEquals(100, set.size()); - } - - @Test - public void testScriptFactoryCustomizerStatic() throws Exception { - final Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo")); - final GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer); - final ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource( - "\"name=${name}\"".getBytes(), "InlineScript")); - Object scriptedObject = factory.getScriptedObject(scriptSource, null); - assertEquals("name=foo", scriptedObject.toString()); - CompletionService completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(10)); - for (int i = 0; i < 100; i++) { - final String name = "bar" + i; - completionService.submit(new Callable() { - public String call() throws Exception { - Object scriptedObject = factory.getScriptedObject(scriptSource, null); - String result = scriptedObject.toString(); - logger.debug("Result=" + result + " with name=" + name); - if (!("name=foo").equals(result)) { - throw new IllegalStateException("Wrong value (" + result + ") for: " + name); - } - return name; - } - }); - } - Set set = new HashSet(); - for (int i = 0; i < 100; i++) { - set.add(completionService.take().get()); - } - assertEquals(100, set.size()); - } - - @Test - public void testScriptFactoryCustomizerThreadSafetyWithNewScript() throws Exception { - final Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo")); - final GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer); - CompletionService completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(5)); - for (int i = 0; i < 100; i++) { - final String name = "Bar" + i; - completionService.submit(new Callable() { - public String call() throws Exception { - Object scriptedObject; - synchronized (customizer) { - customizer.setMap(Collections.singletonMap("name", (Object) name)); - ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource( - "\"name=${name}\"".getBytes(), "InlineScript" + name)); - scriptedObject = factory.getScriptedObject(scriptSource, null); - } - String result = scriptedObject.toString(); - logger.debug("Result=" + result + " with name=" + name); - if (!("name=" + name).equals(result)) { - throw new IllegalStateException("Wrong value (" + result + ") for: " + name); - } - return name; - } - }); - } - Set set = new HashSet(); - for (int i = 0; i < 100; i++) { - set.add(completionService.take().get()); - } - assertEquals(100, set.size()); - } - - - private static class Customizer implements GroovyObjectCustomizer { - - private Map map = new HashMap(); - - public Customizer(Map map) { - super(); - this.map.putAll(map); - } - - public void customize(GroovyObject goo) { - Assert.state(goo instanceof Script, "Expected a Script"); - for (Map.Entry entry : map.entrySet()) { - ((Script) goo).getBinding().setVariable(entry.getKey(), entry.getValue()); - } - } - - public void setMap(Map map) { - this.map.clear(); - this.map.putAll(map); - } - } - - - private static class NamedByteArrayResource extends ByteArrayResource { - - private final String fileName; - - public NamedByteArrayResource(byte[] bytes, String fileName) { - super(bytes); - this.fileName = fileName; - } - - @Override - public String getFilename() throws IllegalStateException { - return fileName; - } - - } - -} diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java index 7e04be5c68..039835c4d8 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -18,26 +18,35 @@ package org.springframework.integration.groovy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Rule; import org.junit.Test; + import org.springframework.core.io.AbstractResource; import org.springframework.integration.Message; import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.scripting.RefreshableResourceScriptSource; import org.springframework.integration.scripting.ScriptVariableGenerator; import org.springframework.integration.support.MessageBuilder; import org.springframework.scripting.ScriptSource; import org.springframework.scripting.support.ResourceScriptSource; +import org.springframework.scripting.support.StaticScriptSource; import org.springframework.test.annotation.Repeat; +import groovy.lang.Script; + /** * @author Mark Fisher * @author Dave Syer @@ -176,6 +185,43 @@ public class GroovyScriptExecutingMessageProcessorTests { assertEquals("payload is 'hello'", result.toString()); } + @Test + public void testInt3166GroovyScriptExecutingMessageProcessorPerformance() throws Exception { + final Message message = new GenericMessage("test"); + + final AtomicInteger var1 = new AtomicInteger(); + final AtomicInteger var2 = new AtomicInteger(); + + String script = + "var1.incrementAndGet(); Thread.sleep(100); var2.set(Math.max(var1.get(), var2.get())); var1.decrementAndGet()"; + + ScriptSource scriptSource = new StaticScriptSource(script, Script.class.getName()); + final MessageProcessor processor = + new GroovyScriptExecutingMessageProcessor(scriptSource, new ScriptVariableGenerator() { + @Override + public Map generateScriptVariables(Message message) { + Map variables = new HashMap(2); + variables.put("var1", var1); + variables.put("var2", var2); + return variables; + } + }); + + ExecutorService executor = Executors.newFixedThreadPool(10); + for (int i = 0; i < 10; i++) { + executor.execute(new Runnable() { + @Override + public void run() { + processor.processMessage(message); + } + }); + } + executor.shutdown(); + assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + + assertTrue(var2.get() > 1); + + } private static class TestResource extends AbstractResource { diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java index b472eeabb5..54978959e3 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java @@ -97,6 +97,7 @@ public class GroovyFilterTests { assertTrue(this.groovyFilterMessageHandler instanceof MessageFilter); MessageSelector selector = TestUtils.getPropertyValue(this.groovyFilterMessageHandler, "selector", MethodInvokingSelector.class); + @SuppressWarnings("rawtypes") MessageProcessor messageProcessor = TestUtils.getPropertyValue(selector, "messageProcessor", MessageProcessor.class); //before it was MethodInvokingMessageProcessor assertTrue(messageProcessor instanceof GroovyScriptExecutingMessageProcessor); diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java index 07b1c2fa6a..12bc99ba76 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java @@ -107,6 +107,7 @@ public class GroovyRouterTests { @Test public void testInt2433VerifyRiddingOfMessageProcessorsWrapping() { assertTrue(this.groovyRouterMessageHandler instanceof MethodInvokingRouter); + @SuppressWarnings("rawtypes") MessageProcessor messageProcessor = TestUtils.getPropertyValue(this.groovyRouterMessageHandler, "messageProcessor", MessageProcessor.class); //before it was MethodInvokingMessageProcessor diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java index c7bc6fd674..86c3724da5 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java @@ -90,7 +90,6 @@ public class GroovyServiceActivatorTests { String value1 = (String) replyChannel.receive(0).getPayload(); String value2 = (String) replyChannel.receive(0).getPayload(); String value3 = (String) replyChannel.receive(0).getPayload(); - System.out.println(value1 + "\n" + value2 + "\n" + value3); assertTrue(value1.startsWith("groovy-test-1-foo - bar")); assertTrue(value2.startsWith("groovy-test-2-foo - bar")); assertTrue(value3.startsWith("groovy-test-3-foo - bar")); diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java index 543831ec9f..7355ea75e5 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java @@ -82,10 +82,11 @@ public class GroovySplitterTests { @Test public void testInt2433VerifyRiddingOfMessageProcessorsWrapping() { - assertTrue(this.groovySplitterMessageHandler instanceof MethodInvokingSplitter); - MessageProcessor messageProcessor = TestUtils.getPropertyValue(this.groovySplitterMessageHandler, + assertTrue(this.groovySplitterMessageHandler instanceof MethodInvokingSplitter); + @SuppressWarnings("rawtypes") + MessageProcessor messageProcessor = TestUtils.getPropertyValue(this.groovySplitterMessageHandler, "messageProcessor", MessageProcessor.class); - //before it was MethodInvokingMessageProcessor + //before it was MethodInvokingMessageProcessor assertTrue(messageProcessor instanceof GroovyScriptExecutingMessageProcessor); } diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java index 5fafa41c7d..74d7725b42 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java @@ -88,11 +88,12 @@ public class GroovyTransformerTests { @Test public void testInt2433VerifyRiddingOfMessageProcessorsWrapping() { - assertTrue(this.groovyTransformerMessageHandler instanceof MessageTransformingHandler); - Transformer transformer = TestUtils.getPropertyValue(this.groovyTransformerMessageHandler, "transformer", Transformer.class); - assertTrue(transformer instanceof AbstractMessageProcessingTransformer); - MessageProcessor messageProcessor = TestUtils.getPropertyValue(transformer, "messageProcessor", MessageProcessor.class); - //before it was MethodInvokingMessageProcessor + assertTrue(this.groovyTransformerMessageHandler instanceof MessageTransformingHandler); + Transformer transformer = TestUtils.getPropertyValue(this.groovyTransformerMessageHandler, "transformer", Transformer.class); + assertTrue(transformer instanceof AbstractMessageProcessingTransformer); + @SuppressWarnings("rawtypes") + MessageProcessor messageProcessor = TestUtils.getPropertyValue(transformer, "messageProcessor", MessageProcessor.class); + //before it was MethodInvokingMessageProcessor assertTrue(messageProcessor instanceof GroovyScriptExecutingMessageProcessor); } } diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java index 06300b0554..de2443c5a7 100644 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java +++ b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java @@ -1,11 +1,11 @@ /* * Copyright 2002-2013 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. @@ -27,7 +27,7 @@ import org.springframework.util.Assert; /** * Base {@link MessageProcessor} for scripting implementations to extend. - * + * * @author Mark Fisher * @author Stefan Reuter * @since 2.0 @@ -36,9 +36,9 @@ public abstract class AbstractScriptExecutingMessageProcessor implements Mess private final ScriptVariableGenerator scriptVariableGenerator; - private volatile ClassLoader beanClassLoader; + protected volatile ClassLoader beanClassLoader; - private volatile BeanFactory beanFactory; + protected volatile BeanFactory beanFactory; protected AbstractScriptExecutingMessageProcessor() { this.scriptVariableGenerator = new DefaultScriptVariableGenerator(); @@ -48,7 +48,7 @@ public abstract class AbstractScriptExecutingMessageProcessor implements Mess Assert.notNull(scriptVariableGenerator, "scriptVariableGenerator must not be null"); this.scriptVariableGenerator = scriptVariableGenerator; } - + /** * Executes the script and returns the result. @@ -69,23 +69,15 @@ public abstract class AbstractScriptExecutingMessageProcessor implements Mess this.beanClassLoader = classLoader; } - protected ClassLoader getBeanClassLoader() { - return this.beanClassLoader; - } - @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } - protected BeanFactory getBeanFactory() { - return this.beanFactory; - } - /** * Subclasses must implement this method to create a script source, * optionally using the message to locate or create the script. - * + * * @param message the message being processed * @return a ScriptSource to use to create a script */ diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java index 964aadefb8..b46cca2ad2 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java @@ -72,7 +72,6 @@ public class Jsr223ServiceActivatorTests { String value1 = (String) replyChannel.receive(0).getPayload(); String value2 = (String) replyChannel.receive(0).getPayload(); String value3 = (String) replyChannel.receive(0).getPayload(); - System.out.println(value1 + "\n" + value2 + "\n" + value3); assertTrue(value1.startsWith("python-test-1-foo - bar")); assertTrue(value2.startsWith("python-test-2-foo - bar")); assertTrue(value3.startsWith("python-test-3-foo - bar")); diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java index dcddde9582..039fac7099 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java @@ -1,11 +1,11 @@ /* * Copyright 2002-2011 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,12 @@ package org.springframework.integration.scripting.jsr223; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; import java.util.HashMap; import java.util.Map; import org.junit.Test; + import org.springframework.core.io.ClassPathResource; import org.springframework.integration.scripting.ScriptExecutor; import org.springframework.integration.scripting.ScriptingException; @@ -30,28 +30,29 @@ import org.springframework.scripting.support.StaticScriptSource; * */ public class Jsr223ScriptExecutorTests { + @Test - public void test(){ + public void test() { ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("jruby"); - executor.executeScript(new StaticScriptSource("puts 'hello, world'")); - executor.executeScript(new StaticScriptSource("puts 'hello, again'")); - + executor.executeScript(new StaticScriptSource("'hello, world'")); + executor.executeScript(new StaticScriptSource("'hello, again'")); + Map variables = new HashMap(); - + Map headers = new HashMap(); - headers.put("one",1); - headers.put("two","two"); - headers.put("three", new Integer(3)); - + headers.put("one", 1); + headers.put("two", "two"); + headers.put("three", 3); + variables.put("payload", "payload"); variables.put("headers", headers); - + String result = (String)executor.executeScript( new ResourceScriptSource(new ClassPathResource("/org/springframework/integration/scripting/jsr223/print_message.rb")), variables ); - - assertEquals("payload modified",result.substring(0,"payload modified".length())); + + assertEquals("payload modified", result.substring(0, "payload modified".length())); } @Test public void testJs(){ @@ -59,25 +60,20 @@ public class Jsr223ScriptExecutorTests { Object obj = executor.executeScript(new StaticScriptSource("function js(){ return 'js';} js();")); assertEquals("js",obj.toString()); } - - @Test + + @Test public void testPython() { ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("python"); Object obj = executor.executeScript(new StaticScriptSource("x=2") ); assertEquals(2,obj); - + obj = executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)") ); assertEquals(2,obj); } - - @Test + + @Test(expected = ScriptingException.class) public void testInvalidLanguageThrowsScriptingException() { - try { - ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("foo"); - executor.executeScript(new StaticScriptSource("x=2")); - fail("should throw Exception"); - } catch (ScriptingException e) { - System.out.println(e.getMessage()); - } + ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("foo"); + executor.executeScript(new StaticScriptSource("x=2")); } } diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java index f30c04b549..41a34dcc47 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2011 the original author or authors. - * + * Copyright 2002-2013 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. @@ -36,39 +36,38 @@ public class PythonScriptExecutorTests { public void init() { executor = new PythonScriptExecutor(); } - - @Test + + @Test public void testLiteral() { Object obj = executor.executeScript(new StaticScriptSource("3+4") ); assertEquals(7,obj); - + obj = executor.executeScript(new StaticScriptSource("'hello,world'") ); assertEquals("hello,world",obj); } - - @Test - + + @Test + public void test1() { Object obj = executor.executeScript(new StaticScriptSource("x=2") ); assertEquals(2,obj); } - - @Test + + @Test public void test2() { Object obj = executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)") ); assertEquals(2,obj); } - - @Test + + @Test public void test3() { - ScriptSource source = + ScriptSource source = new ResourceScriptSource(new ClassPathResource("/org/springframework/integration/scripting/jsr223/test3.py")); Object obj = executor.executeScript(source); - System.out.println(obj); PyTuple tuple = (PyTuple) obj; assertEquals(1, tuple.get(0)); } - + @Test public void testEmbeddedVariable() { Map variables = new HashMap(); From 182b232fda62488855a34e53a3daf23c382d1b9a Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Fri, 25 Oct 2013 14:43:13 +0300 Subject: [PATCH 02/12] INT-2629 Extract Gateway Argument Mapping Strategy https://jira.springsource.org/browse/INT-2629 Strategy for mapping gateway methods to map method arguments to a message. Default strategy works as today, but pluggable using attribute 'mapper' on . A custom mapper should implement InboundMessageMapper where the MethodArgsHolder contains the Method object and the argument values. When using a custom mapper, the mapper is entirely responsible for creating the Message - therefore 'payload-expression' attributes and
elements are not allowed. INT-2629 Polishing - Javadocs INT-2629 Polishing - Introduce MethodArgsMessageMapper - higher level API for custom mapper. INT-2629: Polishing docs --- .../integration/config/xml/GatewayParser.java | 17 +- .../GatewayMethodInboundMessageMapper.java | 186 ++++++++++-------- .../gateway/GatewayProxyFactoryBean.java | 14 +- .../integration/gateway/MethodArgsHolder.java | 50 +++++ .../gateway/MethodArgsMessageMapper.java | 30 +++ .../config/xml/spring-integration-3.0.xsd | 16 ++ .../gateway/GatewayInterfaceTests-context.xml | 6 + .../gateway/GatewayInterfaceTests.java | 35 ++++ ...hodInboundMessageMapperToMessageTests.java | 11 +- src/reference/docbook/gateway.xml | 37 ++++ src/reference/docbook/whats-new.xml | 4 + 11 files changed, 319 insertions(+), 87 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java index dea4f53dac..5a2d6bf525 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; import org.springframework.integration.gateway.GatewayProxyFactoryBean; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -43,7 +44,7 @@ import org.springframework.util.xml.DomUtils; public class GatewayParser extends AbstractSimpleBeanDefinitionParser { private static String[] referenceAttributes = new String[] { - "default-request-channel", "default-reply-channel", "error-channel", "message-mapper", "async-executor" + "default-request-channel", "default-reply-channel", "error-channel", "message-mapper", "async-executor", "mapper" }; private static String[] innerAttributes = new String[] { @@ -93,9 +94,16 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, attributeName); } + boolean hasMapper = StringUtils.hasText(element.getAttribute("mapper")); + boolean hasDefaultPayloadExpression = StringUtils.hasText(element.getAttribute("default-payload-expression")); + Assert.state(hasMapper ? !hasDefaultPayloadExpression : true, "'default-payload-expression' is not allowed when a 'mapper' is provided"); + List invocationHeaders = DomUtils.getChildElementsByTagName(element, "default-header"); - if (!CollectionUtils.isEmpty(invocationHeaders) - || StringUtils.hasText(element.getAttribute("default-payload-expression"))) { + boolean hasDefaultHeaders = !CollectionUtils.isEmpty(invocationHeaders); + + Assert.state(hasMapper ? !hasDefaultHeaders : true, "default-header elements are not allowed when a 'mapper' is provided"); + + if (hasDefaultHeaders || hasDefaultPayloadExpression) { BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition( "org.springframework.integration.gateway.GatewayMethodMetadata"); this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders); @@ -118,8 +126,11 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { methodMetadataBuilder.addPropertyValue("requestTimeout", methodElement.getAttribute("request-timeout")); methodMetadataBuilder.addPropertyValue("replyTimeout", methodElement.getAttribute("reply-timeout")); IntegrationNamespaceUtils.setValueIfAttributeDefined(methodMetadataBuilder, methodElement, "payload-expression"); + Assert.state(hasMapper ? !StringUtils.hasText(element.getAttribute("payload-expression")) : true, + "'payload-expression' is not allowed when a 'mapper' is provided"); invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header"); if (!CollectionUtils.isEmpty(invocationHeaders)) { + Assert.state(!hasMapper, "header elements are not allowed when a 'mapper' is provided"); this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders); } methodMetadataMap.put(methodName, methodMetadataBuilder.getBeanDefinition()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java index 7ec9dd3fe3..0e4f054658 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java @@ -42,6 +42,7 @@ import org.springframework.integration.annotation.Headers; import org.springframework.integration.annotation.Payload; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.mapping.InboundMessageMapper; +import org.springframework.integration.mapping.MessageMappingException; import org.springframework.integration.support.MessageBuilder; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -85,6 +86,8 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper parameterList; + private final MethodArgsMessageMapper argsMapper; + private volatile Expression payloadExpression; private final Map parameterPayloadExpressions = new HashMap(); @@ -93,23 +96,28 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper headerExpressions) { - this(method, headerExpressions, null); + this(method, headerExpressions, null, null); } public GatewayMethodInboundMessageMapper(Method method, Map headerExpressions, - Map globalHeaderExpressions) { + Map globalHeaderExpressions, MethodArgsMessageMapper mapper) { Assert.notNull(method, "method must not be null"); this.method = method; this.headerExpressions = headerExpressions; this.globalHeaderExpressions = globalHeaderExpressions; this.parameterList = getMethodParameterList(method); this.payloadExpression = parsePayloadExpression(method); + if (mapper == null) { + this.argsMapper = new DefaultMethodArgsMessageMapper(); + } + else { + this.argsMapper = mapper; + } } @@ -135,85 +143,17 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper mapArgumentsToMessage(Object[] arguments) { - Object messageOrPayload = null; - boolean foundPayloadAnnotation = false; - Map headers = new HashMap(); - EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments); - if (this.payloadExpression != null) { - messageOrPayload = this.payloadExpression.getValue(methodInvocationEvaluationContext); + try { + return this.argsMapper.toMessage(new MethodArgsHolder(this.method, arguments)); } - for (int i = 0; i < this.parameterList.size(); i++) { - Object argumentValue = arguments[i]; - MethodParameter methodParameter = this.parameterList.get(i); - Annotation annotation = this.findMappingAnnotation(methodParameter.getParameterAnnotations()); - if (annotation != null) { - if (annotation.annotationType().equals(Payload.class)) { - if (messageOrPayload != null) { - this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter); - } - String expression = ((Payload) annotation).value(); - if (!StringUtils.hasText(expression)) { - messageOrPayload = argumentValue; - } - else { - messageOrPayload = this.evaluatePayloadExpression(expression, argumentValue); - } - foundPayloadAnnotation = true; - } - else if (annotation.annotationType().equals(Header.class)) { - Header headerAnnotation = (Header) annotation; - String headerName = this.determineHeaderName(headerAnnotation, methodParameter); - if (headerAnnotation.required() && argumentValue == null) { - throw new IllegalArgumentException("Received null argument value for required header: '" + headerName + "'"); - } - headers.put(headerName, argumentValue); - } - else if (annotation.annotationType().equals(Headers.class)) { - if (argumentValue != null) { - if (!(argumentValue instanceof Map)) { - throw new IllegalArgumentException("@Headers annotation is only valid for Map-typed parameters"); - } - for (Object key : ((Map) argumentValue).keySet()) { - Assert.isInstanceOf(String.class, key, "Invalid header name [" + key + - "], name type must be String."); - Object value = ((Map) argumentValue).get(key); - headers.put((String) key, value); - } - } - } + catch (Exception e) { + if (e instanceof MessagingException) { + throw (MessagingException) e; } - else if (messageOrPayload == null) { - messageOrPayload = argumentValue; - } - else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) { - if (messageOrPayload instanceof Map && !foundPayloadAnnotation) { - if (payloadExpression == null){ - throw new MessagingException("Ambiguous method parameters; found more than one " + - "Map-typed parameter and neither one contains a @Payload annotation"); - } - } - this.copyHeaders((Map) argumentValue, headers); - } - else if (this.payloadExpression == null) { - this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter); + else { + throw new MessageMappingException("Failed to map arguments", e); } } - Assert.isTrue(messageOrPayload != null, "unable to determine a Message or payload parameter on method [" + method + "]"); - MessageBuilder builder = (messageOrPayload instanceof Message) - ? MessageBuilder.fromMessage((Message) messageOrPayload) - : MessageBuilder.withPayload(messageOrPayload); - builder.copyHeadersIfAbsent(headers); - // Explicit headers in XML override any @Header annotations... - if (!CollectionUtils.isEmpty(this.headerExpressions)) { - Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.headerExpressions); - builder.copyHeaders(evaluatedHeaders); - } - // ...whereas global (default) headers do not... - if (!CollectionUtils.isEmpty(this.globalHeaderExpressions)) { - Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.globalHeaderExpressions); - builder.copyHeadersIfAbsent(evaluatedHeaders); - } - return builder.build(); } private Map evaluateHeaders(EvaluationContext methodInvocationEvaluationContext, Map headerExpressions) { @@ -318,4 +258,94 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper toMessage(MethodArgsHolder holder) throws Exception { + Object messageOrPayload = null; + boolean foundPayloadAnnotation = false; + Object[] arguments = holder.getArgs(); + EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments); + Map headers = new HashMap(); + if (GatewayMethodInboundMessageMapper.this.payloadExpression != null) { + messageOrPayload = GatewayMethodInboundMessageMapper.this.payloadExpression.getValue(methodInvocationEvaluationContext); + } + for (int i = 0; i < GatewayMethodInboundMessageMapper.this.parameterList.size(); i++) { + Object argumentValue = arguments[i]; + MethodParameter methodParameter = GatewayMethodInboundMessageMapper.this.parameterList.get(i); + Annotation annotation = GatewayMethodInboundMessageMapper.this.findMappingAnnotation(methodParameter.getParameterAnnotations()); + if (annotation != null) { + if (annotation.annotationType().equals(Payload.class)) { + if (messageOrPayload != null) { + GatewayMethodInboundMessageMapper.this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter); + } + String expression = ((Payload) annotation).value(); + if (!StringUtils.hasText(expression)) { + messageOrPayload = argumentValue; + } + else { + messageOrPayload = GatewayMethodInboundMessageMapper.this.evaluatePayloadExpression(expression, argumentValue); + } + foundPayloadAnnotation = true; + } + else if (annotation.annotationType().equals(Header.class)) { + Header headerAnnotation = (Header) annotation; + String headerName = GatewayMethodInboundMessageMapper.this.determineHeaderName(headerAnnotation, methodParameter); + if (headerAnnotation.required() && argumentValue == null) { + throw new IllegalArgumentException("Received null argument value for required header: '" + headerName + "'"); + } + headers.put(headerName, argumentValue); + } + else if (annotation.annotationType().equals(Headers.class)) { + if (argumentValue != null) { + if (!(argumentValue instanceof Map)) { + throw new IllegalArgumentException("@Headers annotation is only valid for Map-typed parameters"); + } + for (Object key : ((Map) argumentValue).keySet()) { + Assert.isInstanceOf(String.class, key, "Invalid header name [" + key + + "], name type must be String."); + Object value = ((Map) argumentValue).get(key); + headers.put((String) key, value); + } + } + } + } + else if (messageOrPayload == null) { + messageOrPayload = argumentValue; + } + else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) { + if (messageOrPayload instanceof Map && !foundPayloadAnnotation) { + if (payloadExpression == null){ + throw new MessagingException("Ambiguous method parameters; found more than one " + + "Map-typed parameter and neither one contains a @Payload annotation"); + } + } + GatewayMethodInboundMessageMapper.this.copyHeaders((Map) argumentValue, headers); + } + else if (GatewayMethodInboundMessageMapper.this.payloadExpression == null) { + GatewayMethodInboundMessageMapper.this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter); + } + } + Assert.isTrue(messageOrPayload != null, "unable to determine a Message or payload parameter on method [" + method + "]"); + MessageBuilder builder = (messageOrPayload instanceof Message) + ? MessageBuilder.fromMessage((Message) messageOrPayload) + : MessageBuilder.withPayload(messageOrPayload); + builder.copyHeadersIfAbsent(headers); + // Explicit headers in XML override any @Header annotations... + if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.headerExpressions)) { + Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, + GatewayMethodInboundMessageMapper.this.headerExpressions); + builder.copyHeaders(evaluatedHeaders); + } + // ...whereas global (default) headers do not... + if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.globalHeaderExpressions)) { + Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, + GatewayMethodInboundMessageMapper.this.globalHeaderExpressions); + builder.copyHeadersIfAbsent(evaluatedHeaders); + } + return builder.build(); + } + + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java index a2b7983061..877d647b73 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java @@ -105,6 +105,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab private volatile GatewayMethodMetadata globalMethodMetadata; + private volatile MethodArgsMessageMapper argsMapper; + /** * Create a Factory whose service interface type can be configured by setter injection. * If none is set, it will fall back to the default service interface type, @@ -214,6 +216,15 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab this.beanClassLoader = beanClassLoader; } + /** + * Provide a custom {@link MethodArgsMessageMapper} to map from a {@link MethodArgsHolder} + * to a {@link Message}. + * @param mapper the mapper. + */ + public final void setMapper(MethodArgsMessageMapper mapper) { + this.argsMapper = mapper; + } + @Override protected void onInit() { synchronized (this.initializationMonitor) { @@ -395,7 +406,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab } } GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions, - this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null); + this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null, + this.argsMapper); if (StringUtils.hasText(payloadExpression)) { messageMapper.setPayloadExpression(payloadExpression); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java new file mode 100644 index 0000000000..0a8ce4c792 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013 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.gateway; + +import java.lang.reflect.Method; + +/** + * Simple wrapper class containing a {@link Method} and an object + * array containing the arguments for an invocation of that method. + * For example used by a {@link MethodArgsMessageMapper} with this generic + * type to provide custom argument mapping when creating a message + * in a {@code GatewayProxyFactoryBean}. + * + * @author Gary Russell + * @since 3.0 + * + */ +public final class MethodArgsHolder { + + private final Method method; + + private final Object[] args; + + public MethodArgsHolder(Method method, Object[] args) { + this.method = method; + this.args = args; + } + + public final Method getMethod() { + return method; + } + + public final Object[] getArgs() { + return args; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java new file mode 100644 index 0000000000..70df6db7c9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013 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.gateway; + +import org.springframework.integration.mapping.InboundMessageMapper; + +/** + * Implementations of this interface are {@link InboundMessageMapper}s + * that map a {@link MethodArgsHolder} to a {@link org.springframework.integration.Message}. + * + * @author Gary Russell + * @since 3.0 + * + */ +public interface MethodArgsMessageMapper extends InboundMessageMapper { + +} diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd index 2cd4802b3e..4c6867c967 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd @@ -717,6 +717,22 @@ + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml index 788bd10290..ce665b5115 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml @@ -20,4 +20,10 @@ + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index 69242b4597..0e8f04c46d 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -40,6 +40,7 @@ import org.springframework.integration.annotation.Gateway; import org.springframework.integration.annotation.Header; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.support.MessageBuilder; /** * @author Oleg Zhurakousky @@ -238,6 +239,26 @@ public class GatewayInterfaceTests { new GatewayProxyFactoryBean(NotAnInterface.class); } + @Test + public void testWithCustomMapper() { + ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); + final AtomicBoolean called = new AtomicBoolean(); + MessageHandler handler = new MessageHandler() { + + @Override + public void handleMessage(Message message) throws MessagingException { + assertThat((String) message.getPayload(), equalTo("fizbuz")); + called.set(true); + } + }; + channel.subscribe(handler); + Baz baz = ac.getBean(Baz.class); + baz.baz("hello"); + assertTrue(called.get()); + } + + public interface Foo { @Gateway(requestChannel="requestChannelFoo") @@ -256,4 +277,18 @@ public class GatewayInterfaceTests { public static class NotAnInterface { public void fail(String payload){} } + + public interface Baz { + + public void baz(String payload); + } + + public static class BazMapper implements MethodArgsMessageMapper { + + @Override + public Message toMessage(MethodArgsHolder object) throws Exception { + return MessageBuilder.withPayload("fizbuz").build(); + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java index e076aa4b1a..76b33f6310 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java @@ -34,6 +34,7 @@ import org.springframework.integration.Message; import org.springframework.integration.annotation.Header; import org.springframework.integration.annotation.Headers; import org.springframework.integration.annotation.Payload; +import org.springframework.integration.mapping.MessageMappingException; import org.springframework.integration.support.MessageBuilder; /** @@ -78,7 +79,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests { assertEquals("bar", message.getHeaders().get("foo")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageMappingException.class) public void toMessageWithPayloadAndRequiredHeaderButNullValue() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndHeader", String.class, String.class); @@ -134,7 +135,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests { assertEquals("test", message.getPayload()); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageMappingException.class) public void toMessageWithPayloadAndHeadersMapWithNonStringKey() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndHeadersMap", String.class, Map.class); @@ -166,7 +167,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests { assertEquals("bar", message.getHeaders().get("foo")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageMappingException.class) public void toMessageWithMessageParameterAndRequiredHeaderButNullValue() throws Exception { Method method = TestService.class.getMethod("sendMessageAndHeader", Message.class, String.class); GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method); @@ -197,7 +198,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests { assertNull(message.getHeaders().get("foo")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageMappingException.class) public void noArgs() throws Exception { Method method = TestService.class.getMethod("noArgs", new Class[] {}); GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method); @@ -205,7 +206,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests { mapper.toMessage(new Object[] {}); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = MessageMappingException.class) public void onlyHeaders() throws Exception { Method method = TestService.class.getMethod("onlyHeaders", String.class, String.class); GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method); diff --git a/src/reference/docbook/gateway.xml b/src/reference/docbook/gateway.xml index e0f12db1be..28012f740a 100644 --- a/src/reference/docbook/gateway.xml +++ b/src/reference/docbook/gateway.xml @@ -187,6 +187,43 @@ public interface Cafe { +
+ Mapping Method Arguments to a Message + + Using the configuration techniques in the previous section allows control of how method arguments are mapped + to message elements (payload and header(s)). When no explicit configuration is used, certain conventions are + used to perform the mapping. In some cases, these conventions cannot determine which argument is the payload + and which should be mapped to headers. + + + + In the first case, the convention will map the first argument to the payload (as long as it is not a + Map) and the contents of the second become headers. + + + In the second case (or the first when the argument for parameter foo is a Map), + the framework cannot determine + which argument should be the payload; mapping will fail. This can generally be resolved using a + payload-expression, a @Payload annotation and/or a @Headers + annotation. + + + Alternatively, and whenever the conventions break down, you can take the entire responsibility for + mapping the method calls to messages. To do this, implement an + MethodArgsMessageMapper and provide it to the + <gateway/> using the mapper attribute. The mapper maps a + MethodArgsHolder, which is a simple class wrapping the java.reflect.Method + instance and an Object[] containing the arguments. When providing a custom mapper, + the default-payload-expression attribute and <default-header/> elements + are not allowed on the gateway; similarly, the payload-expression attribute and + <header/> elements are not allowed on any <method/> elements. + +
+
Invoking No-Argument Methods diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 6d3f2ab7b4..bb3b820e95 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -163,6 +163,10 @@ It is now possible to set common headers across all gateway methods, and more options are provided for adding, to the message, information about which method was invoked. + + It is now possible to entirely customize the way that gateway method calls are mapped + to messages. + From 928b6b8bc37b73bafa28fa2ff62d22fa256cbe4a Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Tue, 29 Oct 2013 10:19:21 +0200 Subject: [PATCH 03/12] INT-2961 Make ARPMH.onInit() Final; Add doInit() https://jira.springsource.org/browse/INT-2961 onInit() relied on subclasses calling super.onInit() to apply the advice chain and add the BeanFactory to the MessagingTemplate. At least one extension (smpp) failed to do this, disabling these features. Make onInit() final and call doInit() for subclass initialization (no-op in ARPMH). INT-2961 Polishing - do not increase visibility of doInit in subclasses. - remove final modifiers from doInit so user subclasses can participate in initialization if needed. --- .../integration/amqp/outbound/AmqpOutboundEndpoint.java | 7 +++---- .../springframework/integration/filter/MessageFilter.java | 3 +-- .../handler/AbstractReplyProducingMessageHandler.java | 7 ++++++- .../springframework/integration/handler/DelayHandler.java | 3 +-- .../integration/handler/ServiceActivatingHandler.java | 3 +-- .../splitter/AbstractMessageProcessingSplitter.java | 7 +++---- .../integration/transformer/ContentEnricher.java | 3 +-- .../transformer/MessageTransformingHandler.java | 5 ++--- .../integration/file/FileWritingMessageHandler.java | 4 +--- .../remote/gateway/AbstractRemoteFileOutboundGateway.java | 3 +-- .../remote/gateway/RemoteFileOutboundGatewayTests.java | 4 ++-- .../http/outbound/HttpRequestExecutingMessageHandler.java | 3 +-- .../integration/jdbc/JdbcOutboundGateway.java | 6 ++---- .../integration/jdbc/StoredProcOutboundGateway.java | 5 ++--- .../integration/jdbc/JdbcOutboundGatewayTests.java | 7 +++---- .../integration/jms/JmsOutboundGateway.java | 3 +-- .../integration/jmx/OperationInvokingMessageHandler.java | 3 +-- .../integration/jpa/outbound/JpaOutboundGateway.java | 6 +----- .../integration/ws/AbstractWebServiceOutboundGateway.java | 3 +-- 19 files changed, 34 insertions(+), 51 deletions(-) diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java index 5387b7c2e6..52d6b038fe 100644 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java +++ b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java @@ -82,8 +82,7 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler private volatile MessageChannel returnChannel; @Override - protected void onInit() { - super.onInit(); + protected void doInit() { Assert.state(exchangeNameExpression == null || exchangeName == null, "Either an exchangeName or an exchangeNameExpression can be provided, but not both"); Assert.state(this.confirmCorrelationExpression == null || !this.expectReply, @@ -116,7 +115,7 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler } if (this.returnChannel != null) { Assert.isTrue(amqpTemplate instanceof RabbitTemplate, "RabbitTemplate implementation is required for publisher returns"); - ( (RabbitTemplate) this.amqpTemplate).setReturnCallback(this); + ((RabbitTemplate) this.amqpTemplate).setReturnCallback(this); } } @@ -289,7 +288,7 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText, String exchange, String routingKey) { - // safe to cast; we asserted we have a RabbitTemplate in onInit() + // safe to cast; we asserted we have a RabbitTemplate in doInit() MessageConverter converter = ((RabbitTemplate) this.amqpTemplate).getMessageConverter(); Object returnedObject = converter.fromMessage(message); MessageBuilder builder = (returnedObject instanceof Message) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java index a40e1fe12a..3bc0829ea0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java @@ -98,8 +98,7 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa } @Override - public final void onInit() { - super.onInit(); + protected void doInit() { if (this.selector instanceof AbstractMessageProcessingSelector) { ((AbstractMessageProcessingSelector) this.selector).setConversionService(this.getConversionService()); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java index a16f66d383..fff0d7d976 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java @@ -19,6 +19,7 @@ package org.springframework.integration.handler; import java.util.List; import org.aopalliance.aop.Advice; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.integration.Message; @@ -114,7 +115,7 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa @Override - protected void onInit() { + protected final void onInit() { if (this.getBeanFactory() != null) { this.messagingTemplate.setBeanFactory(getBeanFactory()); } @@ -125,6 +126,10 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa } this.advisedRequestHandler = (RequestHandler) proxyFactory.getProxy(this.beanClassLoader); } + this.doInit(); + } + + protected void doInit() { } /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java index 304fdb9edb..0b2ed9a966 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java @@ -196,8 +196,7 @@ public class DelayHandler extends AbstractReplyProducingMessageHandler implement } @Override - protected void onInit() { - super.onInit(); + protected void doInit() { if (this.messageStore == null) { this.messageStore = new SimpleMessageStore(); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java index 5f8ea86936..124365c945 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java @@ -55,8 +55,7 @@ public class ServiceActivatingHandler extends AbstractReplyProducingMessageHandl } @Override - public final void onInit() { - super.onInit(); + protected void doInit() { if (processor instanceof AbstractMessageProcessor) { ((AbstractMessageProcessor) this.processor).setConversionService(this.getConversionService()); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java b/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java index 2966fc1773..ad7b8e90be 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -28,7 +28,7 @@ import org.springframework.util.Assert; /** * Base class for Message Splitter implementations that delegate to a * {@link MessageProcessor} instance. - * + * * @author Mark Fisher * @since 2.0 */ @@ -43,8 +43,7 @@ abstract class AbstractMessageProcessingSplitter extends AbstractMessageSplitter } @Override - public void onInit() { - super.onInit(); + protected void doInit() { ConversionService conversionService = this.getConversionService(); if (conversionService != null && this.messageProcessor instanceof AbstractMessageProcessor) { ((AbstractMessageProcessor) this.messageProcessor).setConversionService(conversionService); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java index 51800b2cca..b6d18c5173 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java @@ -194,8 +194,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem * the requestChannel is set. */ @Override - public void onInit() { - super.onInit(); + protected void doInit() { if (this.replyChannel != null) { Assert.notNull(this.requestChannel, "If the replyChannel is set, then the requestChannel must not be null"); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java index cb6289ea49..e742497118 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -54,8 +54,7 @@ public class MessageTransformingHandler extends AbstractReplyProducingMessageHan } @Override - protected void onInit() { - super.onInit(); + protected void doInit() { if (this.getBeanFactory() != null && this.transformer instanceof BeanFactoryAware) { ((BeanFactoryAware) this.transformer).setBeanFactory(this.getBeanFactory()); } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java index 35fe06a475..07db3a3ac4 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java @@ -218,9 +218,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand } @Override - public final void onInit() { - - super.onInit(); + protected void doInit() { this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java index a67874d05b..50c72d1698 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java @@ -286,8 +286,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply @Override - protected void onInit() { - super.onInit(); + protected void doInit() { Assert.notNull(this.command, "command must not be null"); if (Command.RM.equals(this.command) || Command.GET.equals(this.command)) { diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java index eca3f97fa3..16a6171622 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java @@ -78,7 +78,7 @@ public class RemoteFileOutboundGatewayTests { (sessionFactory, "get", "payload"); gw.setFilter(new TestPatternFilter("")); try { - gw.onInit(); + gw.afterPropertiesSet(); fail("Exception expected"); } catch (IllegalArgumentException e) { @@ -93,7 +93,7 @@ public class RemoteFileOutboundGatewayTests { (sessionFactory, "rm", "payload"); gw.setFilter(new TestPatternFilter("")); try { - gw.onInit(); + gw.afterPropertiesSet(); fail("Exception expected"); } catch (IllegalArgumentException e) { diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java index d3c5e9b956..c83f7d45a7 100755 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java @@ -291,8 +291,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe } @Override - public void onInit() { - super.onInit(); + protected void doInit() { this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); ConversionService conversionService = this.getConversionService(); diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java index cd884207d3..b3f759a81c 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -107,9 +107,7 @@ public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler im } @Override - protected void onInit() { - super.onInit(); - + protected void doInit() { if (this.maxRowsPerPoll != null) { Assert.notNull(poller, "If you want to set 'maxRowsPerPoll', then you must provide a 'selectQuery'."); poller.setMaxRowsPerPoll(this.maxRowsPerPoll); diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java index e396e67fc3..df4b076548 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -88,8 +88,7 @@ public class StoredProcOutboundGateway extends AbstractReplyProducingMessageHand * when {@link ProcedureParameter} are passed in. */ @Override - protected void onInit() { - super.onInit(); + protected void doInit() { }; @Override diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java index f8f128a658..955e6aa016 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java @@ -12,15 +12,14 @@ */ package org.springframework.integration.jdbc; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import javax.sql.DataSource; import org.junit.Assert; - -import static org.junit.Assert.assertEquals; - import org.junit.Test; + import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @@ -42,7 +41,7 @@ public class JdbcOutboundGatewayTests { try { jdbcOutboundGateway.setMaxRowsPerPoll(10); - jdbcOutboundGateway.onInit(); + jdbcOutboundGateway.afterPropertiesSet(); } catch (IllegalArgumentException e) { assertEquals("If you want to set 'maxRowsPerPoll', then you must provide a 'selectQuery'.", e.getMessage()); diff --git a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java index 9dd7cdb4ba..38a05ac5da 100644 --- a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java +++ b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java @@ -459,7 +459,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp } @Override - public final void onInit() { + protected void doInit() { synchronized (this.initializationMonitor) { if (this.initialized) { return; @@ -469,7 +469,6 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp ^ this.requestDestinationName != null ^ this.requestDestinationExpressionProcessor != null, "Exactly one of 'requestDestination', 'requestDestinationName', or 'requestDestinationExpression' is required."); - super.onInit(); if (this.requestDestinationExpressionProcessor != null) { this.requestDestinationExpressionProcessor.setBeanFactory(getBeanFactory()); this.requestDestinationExpressionProcessor.setConversionService(getConversionService()); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java index 7b2232c304..8f5c8ecc5b 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java @@ -102,9 +102,8 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa } @Override - public final void onInit() { + protected void doInit() { Assert.notNull(this.server, "MBeanServer is required."); - super.onInit(); } @Override diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java index d6b01970ad..96bdc790be 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java @@ -62,12 +62,8 @@ public class JpaOutboundGateway extends AbstractReplyProducingMessageHandler { } - /** - * - */ @Override - protected void onInit() { - super.onInit(); + protected void doInit() { this.jpaExecutor.setBeanFactory(this.getBeanFactory()); } diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java index d56cafd855..14c23c9a55 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java @@ -144,8 +144,7 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro } @Override - public void onInit() { - super.onInit(); + protected void doInit() { this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); Assert.state(this.destinationProvider == null || CollectionUtils.isEmpty(this.uriVariableExpressions), "uri variables are not supported when a DestinationProvider is supplied."); From d06c1c269d026651af5fa5c5f537d5ee53d9de6a Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 28 Oct 2013 16:09:11 +0200 Subject: [PATCH 04/12] INT-3180: Fix `FtpParserInboundTests` JIRA: https://jira.springsource.org/browse/INT-3180 * Change non-existing interface in the config to existing one * Change `@Test(expected=BeanCreationException.class)` to `try...catch` block and check the StackTrace for appropriate, expected `Exception` --- .../FtpParserInboundTests-fail-context.xml | 13 ++++----- .../ftp/FtpParserInboundTests.java | 27 ++++++++++++++++--- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml index 9aa1fc8537..038b9e9b04 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml @@ -3,12 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ftp="http://www.springframework.org/schema/integration/ftp" xmlns:int="http://www.springframework.org/schema/integration" - xmlns:context="http://www.springframework.org/schema/context" - xmlns:ftps="http://www.springframework.org/schema/integration/ftps" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/integration/ftps http://www.springframework.org/schema/integration/ftp/spring-integration-ftps.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd - http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd"> @@ -19,10 +16,10 @@ - - + - + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java index 20c8dceb1c..4ff9043190 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java @@ -15,19 +15,28 @@ */ package org.springframework.integration.ftp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; +import java.io.FileNotFoundException; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; + +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.MessagingException; /** * @author Oleg Zhurakousky * @author Gunnar Hillert + * @author Artem Bilan * */ public class FtpParserInboundTests { @@ -43,14 +52,26 @@ public class FtpParserInboundTests { assertTrue(new File("target/foo").exists()); assertTrue(!new File("target/bar").exists()); } - @Test(expected=BeanCreationException.class) + @Test public void testLocalFilesAutoCreationFalse() throws Exception{ assertTrue(!new File("target/bar").exists()); - new ClassPathXmlApplicationContext("FtpParserInboundTests-fail-context.xml", this.getClass()); + try { + new ClassPathXmlApplicationContext("FtpParserInboundTests-fail-context.xml", this.getClass()); + fail("BeansException expected."); + } + catch (BeansException e) { + Throwable cause = e.getCause(); + assertThat(cause, Matchers.instanceOf(BeanCreationException.class)); + cause = cause.getCause(); + assertThat(cause, Matchers.instanceOf(MessagingException.class)); + cause = cause.getCause(); + assertThat(cause, Matchers.instanceOf(FileNotFoundException.class)); + assertEquals("bar", cause.getMessage()); + } } @After public void cleanUp() throws Exception{ new File("target/foo").delete(); } -} \ No newline at end of file +} From d5e8e352b64f8f26720f8eee3aba16cfd226b411 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 8 Oct 2013 15:44:00 +0300 Subject: [PATCH 05/12] INT-1362 HTTP Inbound: Additional EvalCtx Vars. JIRA: https://jira.springsource.org/browse/INT-1362, https://jira.springsource.org/browse/INT-2469 * Add to the `EvaluationContext` of HTTP Inbound Endpoints additional request variables * Tests and documentation INT-2669: Mention SpEL variables in JavaDocs JIRA: https://jira.springsource.org/browse/INT-2669 INT-1362 Doc Polishing --- .../config/xml/IntegrationNamespaceUtils.java | 7 +- spring-integration-ftp/test/A.TEST.a | 0 spring-integration-ftp/test/B.TEST.a | 0 .../HttpRequestHandlingEndpointSupport.java | 49 ++++++++++++-- ...RequestMappingIntegrationTests-context.xml | 8 +++ ...Int2312RequestMappingIntegrationTests.java | 66 +++++++++++++++++-- src/reference/docbook/http.xml | 50 +++++++++++++- src/reference/docbook/whats-new.xml | 10 ++- 8 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 spring-integration-ftp/test/A.TEST.a create mode 100644 spring-integration-ftp/test/B.TEST.a diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java index 0c15ed6248..150a3dc054 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java @@ -20,12 +20,14 @@ import java.util.List; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -33,14 +35,13 @@ import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.Conventions; import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.SpelFunctionFactoryBean; import org.springframework.integration.endpoint.AbstractPollingEndpoint; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionInterceptor; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.integration.channel.DirectChannel; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; @@ -462,6 +463,7 @@ public abstract class IntegrationNamespaceUtils { } return expressionDef; } + public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, String className, String methodSignature) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class) @@ -469,6 +471,7 @@ public abstract class IntegrationNamespaceUtils { .addConstructorArgValue(methodSignature); registry.registerBeanDefinition(functionId, builder.getBeanDefinition()); } + public static BeanDefinition createExpressionDefIfAttributeDefined(String expressionElementName, Element element) { Assert.hasText(expressionElementName, "'expressionElementName' must no be empty"); diff --git a/spring-integration-ftp/test/A.TEST.a b/spring-integration-ftp/test/A.TEST.a new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-integration-ftp/test/B.TEST.a b/spring-integration-ftp/test/B.TEST.a new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java index 7645811022..599449f59b 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java @@ -19,10 +19,12 @@ package org.springframework.integration.http.inbound; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -65,6 +67,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; +import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.servlet.DispatcherServlet; @@ -210,8 +213,15 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa /** * Specifies a SpEL expression to evaluate in order to generate the Message payload. * The EvaluationContext will be populated with an HttpEntity instance as the root object, - * and it may contain one or both of the #pathVariables and - * #queryParameters variables if present. Those variables' values are Maps. + * and it may contain variables: + *
    + *
  • #pathVariables
  • + *
  • #requestParams
  • + *
  • #requestAttributes
  • + *
  • #requestHeaders
  • + *
  • #matrixVariables
  • + *
  • #cookies + *
*/ public void setPayloadExpression(Expression payloadExpression) { this.payloadExpression = payloadExpression; @@ -221,8 +231,15 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa * Specifies a Map of SpEL expressions to evaluate in order to generate the Message headers. * The keys in the map will be used as the header names. When evaluating the expression, * the EvaluationContext will be populated with an HttpEntity instance as the root object, - * and it may contain one or both of the #pathVariables and - * #queryParameters variables if present. Those variables' values are Maps. + * and it may contain variables: + *
    + *
  • #pathVariables
  • + *
  • #requestParams
  • + *
  • #requestAttributes
  • + *
  • #requestHeaders
  • + *
  • #matrixVariables
  • + *
  • #cookies + *
*/ public void setHeaderExpressions(Map headerExpressions) { this.headerExpressions = headerExpressions; @@ -379,9 +396,22 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa StandardEvaluationContext evaluationContext = this.createEvaluationContext(); evaluationContext.setRootObject(httpEntity); + evaluationContext.setVariable("requestAttributes", RequestContextHolder.currentRequestAttributes()); + MultiValueMap requestParams = this.convertParameterMap(servletRequest.getParameterMap()); evaluationContext.setVariable("requestParams", requestParams); + evaluationContext.setVariable("requestHeaders", new ServletServerHttpRequest(servletRequest).getHeaders()); + + Cookie[] requestCookies = servletRequest.getCookies(); + if (!ObjectUtils.isEmpty(requestCookies)) { + Map cookies = new HashMap(requestCookies.length); + for (Cookie requestCookie : requestCookies) { + cookies.put(requestCookie.getName(), requestCookie); + } + evaluationContext.setVariable("cookies", cookies); + } + Map pathVariables = (Map) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); @@ -392,6 +422,17 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa evaluationContext.setVariable("pathVariables", pathVariables); } + //TODO change it to HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE after upgrade to Spring 4.0 + Map> matrixVariables = + (Map>) servletRequest.getAttribute(HandlerMapping.class.getName() + ".matrixVariables"); + + if (!CollectionUtils.isEmpty(matrixVariables)) { + if (logger.isDebugEnabled()) { + logger.debug("Mapped matrix variables: " + matrixVariables); + } + evaluationContext.setVariable("matrixVariables", matrixVariables); + } + Map headers = this.headerMapper.toHeaders(request.getHeaders()); Object payload = null; if (this.payloadExpression != null) { diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml index b2516af13a..2ff049b4e7 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml @@ -26,8 +26,16 @@ request-channel="toLowerCaseChannel" payload-expression="#pathVariables.value"> + + + + + + + + params = new HashMap(); + params.put("foo", "bar"); + request.setParameters(params); + request.setContent("hello".getBytes()); + final Cookie cookie = new Cookie("foo", "bar"); + request.setCookies(cookie); + request.addHeader("toLowerCase", true); - //See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch - Map uriTemplateVariables = - new AntPathMatcher().extractUriTemplateVariables(TEST_PATH, requestURI); - request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); + //See org.springframework.web.servlet.FrameworkServlet#initContextHolders + final RequestAttributes attributes = new ServletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(attributes); + + this.toLowerCaseChannel.subscribe(new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + MessageHeaders headers = message.getHeaders(); + + assertEquals(attributes, headers.get("requestAttributes")); + + Object requestParams = headers.get("requestParams"); + assertNotNull(requestParams); + assertEquals(params, ((MultiValueMap) requestParams).toSingleValueMap()); + + // TODO test it after upgrade to Spring 4.0 + // assertEquals(matrixVariables, headers.get("matrixVariables")); + + Object requestHeaders = headers.get("requestHeaders"); + assertNotNull(requestParams); + assertEquals(MediaType.TEXT_PLAIN, ((HttpHeaders) requestHeaders).getContentType()); + + Map cookies = (Map) headers.get("cookies"); + assertEquals(1, cookies.size()); + Cookie foo = cookies.get("foo"); + assertNotNull(foo); + assertEquals(cookie, foo); + } + }); MockHttpServletResponse response = new MockHttpServletResponse(); - request.addHeader("toLowerCase", true); - Object handler = this.handlerMapping.getHandler(request).getHandler(); this.handlerAdapter.handle(request, response, handler); final String testResponse = response.getContentAsString(); diff --git a/src/reference/docbook/http.xml b/src/reference/docbook/http.xml index 2a720af4e7..21a162a18a 100644 --- a/src/reference/docbook/http.xml +++ b/src/reference/docbook/http.xml @@ -354,9 +354,57 @@ By default the HTTP request will be generated using an instance of Si + + + Since Spring Integration 3.0, in addition to the existing + #pathVariables and #requestParams variables being available in payload and header + expressions, other useful variables have been added. + + + The entire list of available expression variables: + + + + #requestParams - the MultiValueMap from the + ServletRequest parameterMap. + + + #pathVariables - the Map from URI Template placeholders and their values; + + + #matrixVariables - the Map of MultiValueMap + according to + Spring MVC Specification. Note, #matrixVariables require Spring MVC 3.2 or higher; + + + #requestAttributes - the org.springframework.web.context.request.RequestAttributes + associated with the current Request; + + + #requestHeaders - the org.springframework.http.HttpHeaders object from the current Request; + + + #cookies - the Map<String, Cookie> + of javax.servlet.http.Cookies from the current Request. + + + + Note, all these values (and others) can be accessed within expressions in the downstream message + flow via the ThreadLocal org.springframework.web.context.request.RequestAttributes + variable, if that message flow is single-threaded and lives within the request thread: + + + ]]> + + Outbound - To configure the outbound gateway you can use the namespace support as well. The following code snippet shows the different configuration options for an outbound Http gateway. Most importantly, notice that the 'http-method' and 'expected-response-type' are provided. Those are two of the most commonly configured values. The + To configure the outbound gateway you can use the namespace support as well. + The following code snippet shows the different configuration options for an outbound Http gateway. + Most importantly, notice that the 'http-method' and 'expected-response-type' are provided. Those are two of the most commonly configured values. The default http-method is POST, and the default response type is null. With a null response type, the payload of the reply Message would contain the ResponseEntity as long as it's http status is a success (non-successful status codes will throw Exceptions). If you are expecting a different type, such as a String, then provide that fully-qualified class name as shown below. diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index bb3b820e95..289b0d090a 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -333,12 +333,20 @@ HttpMessageConverters after the custom message converters. - 'If-(Un)Modified-Since' HTTP headers - previously, + 'If-(Un)Modified-Since' HTTP Headers - previously, 'If-Modified-Since' and 'If-Unmodified-Since' HTTP headers were incorrectly processed within from/to HTTP headers mapping in the DefaultHttpHeaderMapper. Now, in addition correcting that issue, DefaultHttpHeaderMapper provides date parsing from formatted strings for any HTTP headers that accept date-time values. + + Inbound Endpoint Expression Variables - + In addition to the existing #requestParams and #pathVariables, + the <http:inbound-gateway/> and <http:inbound-channel-adapter/> + now support additional useful variables: #matrixVariables, #requestAttributes, + #requestHeaders and #cookies. These variables are available in + both payload and header expressions. + From 469910793c51e302d09dee6f25f240ec045ca46a Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Tue, 29 Oct 2013 17:05:33 -0400 Subject: [PATCH 06/12] INT-3180 Add s-i-ftp/test to .gitIgnore Inadvertently added left over test files. --- spring-integration-ftp/.gitignore | 1 + spring-integration-ftp/test/A.TEST.a | 0 spring-integration-ftp/test/B.TEST.a | 0 3 files changed, 1 insertion(+) delete mode 100644 spring-integration-ftp/test/A.TEST.a delete mode 100644 spring-integration-ftp/test/B.TEST.a diff --git a/spring-integration-ftp/.gitignore b/spring-integration-ftp/.gitignore index b97386c397..2fa3db79d5 100644 --- a/spring-integration-ftp/.gitignore +++ b/spring-integration-ftp/.gitignore @@ -1,2 +1,3 @@ local-test-dir/*.test remote-target-dir/*test +test diff --git a/spring-integration-ftp/test/A.TEST.a b/spring-integration-ftp/test/A.TEST.a deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/spring-integration-ftp/test/B.TEST.a b/spring-integration-ftp/test/B.TEST.a deleted file mode 100644 index e69de29bb2..0000000000 From 2bf8196353f77e6e65d2f972c197e24252583bd7 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Wed, 30 Oct 2013 13:57:24 +0200 Subject: [PATCH 07/12] INT-3178 Defer Publishing of TCP Open Event Previously, the TcpConnectionOpenEvent was published as soon as the socket was connected, but the TcpConnection was not yet ready for use. Defer publishing the event until the connection is fully initialized and registered with the factory and thus available for use. Update the test cases to reflect this behavior. Since the event is now published by the appropriate concrete factory implementations, enhance the ConnectionToConnectionTests to verify proper eventing with both Net and NIO implementations. INT-3178: Add `theConnection` `ReadWriteLock` INT-3178 Polishing - Refactor obtainConnection() into obtainSharedConnection() and obtainNewConnection() for Net and NIO Client factories. - obtainConnection is now common for these two factories, and overridden by the caching and failover factories. - ReadWriteLock - double check for shared connection after lock obtained. INT-3178: Polishing --- .../AbstractClientConnectionFactory.java | 73 +++++++++++++++++-- .../tcp/connection/TcpConnectionSupport.java | 1 - .../TcpNetClientConnectionFactory.java | 14 +--- .../TcpNetServerConnectionFactory.java | 10 ++- .../TcpNioClientConnectionFactory.java | 22 ++---- .../TcpNioServerConnectionFactory.java | 1 + .../ConnectionToConnectionTests-context.xml | 38 ++++++++-- .../ip/tcp/ConnectionToConnectionTests.java | 56 +++++++++----- .../tcp/connection/ConnectionEventTests.java | 26 +++---- 9 files changed, 163 insertions(+), 78 deletions(-) diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java index e3f2c91868..700b54c865 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java @@ -17,17 +17,22 @@ package org.springframework.integration.ip.tcp.connection; import java.net.Socket; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Abstract class for client connection factories; client connection factories * establish outgoing connections. * @author Gary Russell + * @author Artem Bilan * @since 2.0 * */ public abstract class AbstractClientConnectionFactory extends AbstractConnectionFactory { - private TcpConnectionSupport theConnection; + private final ReadWriteLock theConnectionLock = new ReentrantReadWriteLock(); + + private volatile TcpConnectionSupport theConnection; /** * Constructs a factory that will established connections to the host and port. @@ -45,18 +50,70 @@ public abstract class AbstractClientConnectionFactory extends AbstractConnection */ public TcpConnectionSupport getConnection() throws Exception { this.checkActive(); - if (this.isSingleUse()) { - return obtainConnection(); - } else { - synchronized(this) { - TcpConnectionSupport connection = obtainConnection(); - this.setTheConnection(connection); + return this.obtainConnection(); + } + + protected TcpConnectionSupport obtainConnection() throws Exception { + if (!this.isSingleUse()) { + TcpConnectionSupport connection = this.obtainSharedConnection(); + if (connection != null) { return connection; } } + return this.obtainNewConnection(); + } + + protected final TcpConnectionSupport obtainSharedConnection() throws InterruptedException { + this.theConnectionLock.readLock().lockInterruptibly(); + try { + TcpConnectionSupport theConnection = this.getTheConnection(); + if (theConnection != null && theConnection.isOpen()) { + return theConnection; + } + } + finally { + this.theConnectionLock.readLock().unlock(); + } + + return null; + } + + protected final TcpConnectionSupport obtainNewConnection() throws Exception { + boolean singleUse = this.isSingleUse(); + if (!singleUse) { + this.theConnectionLock.writeLock().lockInterruptibly(); + } + try { + TcpConnectionSupport connection; + if (!singleUse) { + // Another write lock holder might have created a new one by now. + connection = this.obtainSharedConnection(); + if (connection != null) { + return connection; + } + } + + if (logger.isDebugEnabled()) { + logger.debug("Opening new socket connection to " + this.getHost() + ":" + this.getPort()); + } + + connection = this.buildNewConnection(); + if (!singleUse) { + this.setTheConnection(connection); + } + connection.publishConnectionOpenEvent(); + return connection; + } + finally { + if (!singleUse) { + this.theConnectionLock.writeLock().unlock(); + } + } } - protected abstract TcpConnectionSupport obtainConnection() throws Exception; + protected TcpConnectionSupport buildNewConnection() throws Exception { + throw new UnsupportedOperationException("Factories that don't override this class' obtainConnection() must implement this method"); + } /** * Transfers attributes such as (de)serializers, singleUse etc to a new connection. diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java index 01d2ed28b0..05f2a675be 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java @@ -127,7 +127,6 @@ public abstract class TcpConnectionSupport implements TcpConnection { if (connectionFactoryName != null) { this.connectionFactoryName = connectionFactoryName; } - this.publishConnectionOpenEvent(); if (logger.isDebugEnabled()) { logger.debug("New connection " + this.getConnectionId()); } diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java index 6acaee17cf..7c02f261a2 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java @@ -44,20 +44,8 @@ public class TcpNetClientConnectionFactory extends super(host, port); } - /** - * @throws IOException - * @throws SocketException - * @throws Exception - */ @Override - protected TcpConnectionSupport obtainConnection() throws Exception { - TcpConnectionSupport theConnection = this.getTheConnection(); - if (theConnection != null && theConnection.isOpen()) { - return theConnection; - } - if (logger.isDebugEnabled()) { - logger.debug("Opening new socket connection to " + this.getHost() + ":" + this.getPort()); - } + protected TcpConnectionSupport buildNewConnection() throws IOException, SocketException, Exception { Socket socket = createSocket(this.getHost(), this.getPort()); setSocketAttributes(socket); TcpConnectionSupport connection = new TcpNetConnection(socket, false, this.isLookupHost(), diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java index 631628ca67..0504f2f693 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java @@ -64,7 +64,8 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto try { if (this.getLocalAddress() == null) { theServerSocket = createServerSocket(this.getPort(), this.getBacklog(), null); - } else { + } + else { InetAddress whichNic = InetAddress.getByName(this.getLocalAddress()); theServerSocket = createServerSocket(this.getPort(), this.getBacklog(), whichNic); } @@ -80,7 +81,8 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto */ try { socket = serverSocket.accept(); - } catch (SocketTimeoutException ste) { + } + catch (SocketTimeoutException ste) { if (logger.isDebugEnabled()) { logger.debug("Timed out on accept; continuing"); } @@ -104,9 +106,11 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto this.initializeConnection(connection, socket); this.getTaskExecutor().execute(connection); this.harvestClosedConnections(); + connection.publishConnectionOpenEvent(); } } - } catch (Exception e) { + } + catch (Exception e) { // don't log an error if we had a good socket once and now it's closed if (e instanceof SocketException && theServerSocket != null) { logger.warn("Server Socket closed"); diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java index ff2ec2c4ea..6935fbdd3b 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java @@ -18,7 +18,6 @@ package org.springframework.integration.ip.tcp.connection; import java.io.IOException; import java.net.InetSocketAddress; -import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; @@ -61,13 +60,9 @@ public class TcpNioClientConnectionFactory extends super(host, port); } - /** - * @throws Exception - * @throws IOException - * @throws SocketException - */ @Override - protected TcpConnectionSupport obtainConnection() throws Exception { + protected void checkActive() throws IOException { + super.checkActive(); int n = 0; while (this.selector == null) { try { @@ -76,16 +71,13 @@ public class TcpNioClientConnectionFactory extends Thread.currentThread().interrupt(); } if (n++ > 600) { - throw new Exception("Factory failed to start"); + throw new IOException("Factory failed to start"); } } - TcpConnectionSupport theConnection = this.getTheConnection(); - if (theConnection != null && theConnection.isOpen()) { - return theConnection; - } - if (logger.isDebugEnabled()) { - logger.debug("Opening new socket channel connection to " + this.getHost() + ":" + this.getPort()); - } + } + + @Override + protected TcpConnectionSupport buildNewConnection() throws Exception { SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(this.getHost(), this.getPort())); setSocketAttributes(socketChannel.socket()); TcpNioConnection connection = this.tcpNioConnectionSupport.createNewConnection( diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java index aa882c5e46..6b97ac6bb0 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java @@ -168,6 +168,7 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto connection.setLastRead(now); this.channelMap.put(channel, connection); channel.register(selector, SelectionKey.OP_READ, connection); + connection.publishConnectionOpenEvent(); } catch (Exception e) { logger.error("Exception accepting new connection", e); diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml index dc8857782c..e762803a6e 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml @@ -13,9 +13,9 @@ - - - + + + + + + diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java index 31032d8649..6b4d849c40 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java @@ -65,10 +65,16 @@ public class ConnectionToConnectionTests { AbstractApplicationContext ctx; @Autowired - private AbstractClientConnectionFactory client; + private AbstractClientConnectionFactory clientNet; @Autowired - private AbstractServerConnectionFactory server; + private AbstractServerConnectionFactory serverNet; + + @Autowired + private AbstractClientConnectionFactory clientNio; + + @Autowired + private AbstractServerConnectionFactory serverNio; @Autowired private QueueChannel serverSideChannel; @@ -90,9 +96,19 @@ public class ConnectionToConnectionTests { ctx.close(); } - @SuppressWarnings("unchecked") @Test - public void testConnect() throws Exception { + public void testConnectNet() throws Exception { + testConnectGuts(this.clientNet, this.serverNet, "gwNet", true); + } + + @Test + public void testConnectNio() throws Exception { + testConnectGuts(this.clientNio, this.serverNio, "gwNio", false); + } + + @SuppressWarnings("unchecked") + private void testConnectGuts(AbstractClientConnectionFactory client, AbstractServerConnectionFactory server, + String gatewayName, boolean expectExceptionOnClose) throws Exception { TestingUtilities.waitListening(server, null); client.start(); for (int i = 0; i < 100; i++) { @@ -102,7 +118,7 @@ public class ConnectionToConnectionTests { assertNotNull(message); MessageHistory history = MessageHistory.read(message); //org.springframework.integration.test.util.TestUtils - Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "looper", 0); + Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, gatewayName, 0); assertNotNull(componentHistoryRecord); assertTrue(componentHistoryRecord.get("type").equals("ip:tcp-inbound-gateway")); assertNotNull(message); @@ -116,7 +132,7 @@ public class ConnectionToConnectionTests { Message eventMessage; while ((eventMessage = (Message) events.receive(1000)) != null) { TcpConnectionEvent event = eventMessage.getPayload(); - if ("client".equals(event.getConnectionFactoryName())) { + if (event.getConnectionFactoryName().startsWith("client")) { if (event instanceof TcpConnectionOpenEvent) { clientOpens++; } @@ -127,7 +143,7 @@ public class ConnectionToConnectionTests { clientExceptions++; } } - else if ("server".equals(event.getConnectionFactoryName())) { + else if (event.getConnectionFactoryName().startsWith("server")) { if (event instanceof TcpConnectionOpenEvent) { serverOpens++; } @@ -138,7 +154,9 @@ public class ConnectionToConnectionTests { } assertEquals(100, clientOpens); assertEquals(100, clientCloses); - assertEquals(100, clientExceptions); + if (expectExceptionOnClose) { + assertEquals(100, clientExceptions); + } assertEquals(100, serverOpens); assertEquals(100, serverCloses); } @@ -146,16 +164,16 @@ public class ConnectionToConnectionTests { @Test public void testConnectRaw() throws Exception { ByteArrayRawSerializer serializer = new ByteArrayRawSerializer(); - client.setSerializer(serializer); - server.setDeserializer(serializer); - client.start(); - TcpConnection connection = client.getConnection(); + clientNet.setSerializer(serializer); + serverNet.setDeserializer(serializer); + clientNet.start(); + TcpConnection connection = clientNet.getConnection(); connection.send(MessageBuilder.withPayload("Test").build()); Message message = serverSideChannel.receive(10000); assertNotNull(message); MessageHistory history = MessageHistory.read(message); //org.springframework.integration.test.util.TestUtils - Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "looper", 0); + Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "gwNet", 0); assertNotNull(componentHistoryRecord); assertTrue(componentHistoryRecord.get("type").equals("ip:tcp-inbound-gateway")); assertNotNull(message); @@ -164,16 +182,16 @@ public class ConnectionToConnectionTests { @Test public void testLookup() throws Exception { - client.start(); - TcpConnection connection = client.getConnection(); + clientNet.start(); + TcpConnection connection = clientNet.getConnection(); assertFalse(connection.getConnectionId().contains("localhost")); connection.close(); - client.setLookupHost(true); - connection = client.getConnection(); + clientNet.setLookupHost(true); + connection = clientNet.getConnection(); assertTrue(connection.getConnectionId().contains("localhost")); connection.close(); - client.setLookupHost(false); - connection = client.getConnection(); + clientNet.setLookupHost(false); + connection = clientNet.getConnection(); assertFalse(connection.getConnectionId().contains("localhost")); connection.close(); } diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java index 7bfe7b1735..2f2034ccb4 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java @@ -52,10 +52,10 @@ public class ConnectionEventTests { theEvent.add((TcpConnectionEvent) event); } }, "foo"); - assertTrue(theEvent.size() > 0); - assertNotNull(theEvent.get(0)); - assertTrue(theEvent.get(0) instanceof TcpConnectionOpenEvent); - assertTrue(theEvent.get(0).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "] **OPENED**")); + /* + * Open is not published by the connection itself; the factory publishes it after initialization. + * See ConnectionToConnectionTests. + */ @SuppressWarnings("unchecked") Serializer serializer = mock(Serializer.class); RuntimeException toBeThrown = new RuntimeException("foo"); @@ -67,17 +67,17 @@ public class ConnectionEventTests { fail("Expected exception"); } catch (Exception e) {} - assertTrue(theEvent.size() > 1); - assertNotNull(theEvent.get(1)); - assertTrue(theEvent.get(1) instanceof TcpConnectionExceptionEvent); - assertTrue(theEvent.get(1).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "]")); - assertTrue(theEvent.get(1).toString().contains("cause=java.lang.RuntimeException: foo]")); - TcpConnectionExceptionEvent event = (TcpConnectionExceptionEvent) theEvent.get(1); + assertTrue(theEvent.size() > 0); + assertNotNull(theEvent.get(0)); + assertTrue(theEvent.get(0) instanceof TcpConnectionExceptionEvent); + assertTrue(theEvent.get(0).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "]")); + assertTrue(theEvent.get(0).toString().contains("cause=java.lang.RuntimeException: foo]")); + TcpConnectionExceptionEvent event = (TcpConnectionExceptionEvent) theEvent.get(0); assertNotNull(event.getCause()); assertSame(toBeThrown, event.getCause()); - assertTrue(theEvent.size() > 2); - assertNotNull(theEvent.get(2)); - assertTrue(theEvent.get(2).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "] **CLOSED**")); + assertTrue(theEvent.size() > 1); + assertNotNull(theEvent.get(1)); + assertTrue(theEvent.get(1).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "] **CLOSED**")); } } From 653df623b282f7533e7eed8bd205f3417c9e3564 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Wed, 30 Oct 2013 13:46:19 +0200 Subject: [PATCH 08/12] INT-1362-2: Reset RequestContextHolder in Tests JIRA: https://jira.springsource.org/browse/INT-1362 --- .../groovy/config/GroovyControlBusTests.java | 7 +++- .../http/AbstractHttpInboundTests.java | 42 +++++++++++++++++++ .../http/HttpProxyScenarioTests.java | 1 + .../HttpInboundChannelAdapterParserTests.java | 9 ++-- .../HttpRequestHandlingControllerTests.java | 3 +- ...pRequestHandlingMessagingGatewayTests.java | 5 ++- ...gMessagingGatewayWithPathMappingTests.java | 3 +- ...Int2312RequestMappingIntegrationTests.java | 5 ++- 8 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java 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 e5d3dbe2d6..705d752a29 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 @@ -16,9 +16,9 @@ package org.springframework.integration.groovy.config; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import groovy.lang.GroovyObject; @@ -30,6 +30,7 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; + import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.BeanIsAbstractException; @@ -115,6 +116,8 @@ public class GroovyControlBusTests { Message message = MessageBuilder.withPayload("def result = requestScopedService.convert('testString')").build(); this.input.send(message); assertEquals("cat", output.receive(0).getPayload()); + + RequestContextHolder.resetRequestAttributes(); } @Test //INT-2567 @@ -180,7 +183,7 @@ public class GroovyControlBusTests { private static class MockRequestAttributes implements RequestAttributes { - private Map fakeRequest = new HashMap(); + private final Map fakeRequest = new HashMap(); public Object getAttribute(String name, int scope) { return fakeRequest.get(name); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java new file mode 100644 index 0000000000..044bdcdf2b --- /dev/null +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 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.http; + +import org.junit.After; +import org.junit.Before; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * @author Artem Bilan + * @since 3.0 + */ +public abstract class AbstractHttpInboundTests { + + @Before + public void setupHttpInbound() { + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest())); + } + + @After + public void tearDownHttpInbound() { + RequestContextHolder.resetRequestAttributes(); + } + +} diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java index 903f958864..81fa293e87 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java @@ -137,6 +137,7 @@ public class HttpProxyScenarioTests { assertEquals(ifModifiedSince, headers.get("If-Modified-Since")); assertEquals(ifUnmodifiedSince, headers.get("If-Unmodified-Since")); + RequestContextHolder.resetRequestAttributes(); } } diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java index d86d234170..bd0f8fa4f8 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java @@ -18,11 +18,11 @@ package org.springframework.integration.http.config; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; @@ -36,11 +36,9 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.expression.Expression; -import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.converter.HttpMessageConverter; @@ -48,6 +46,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.history.MessageHistory; +import org.springframework.integration.http.AbstractHttpInboundTests; import org.springframework.integration.http.converter.SerializingHttpMessageConverter; import org.springframework.integration.http.inbound.HttpRequestHandlingController; import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway; @@ -74,7 +73,7 @@ import org.springframework.web.servlet.HandlerMapping; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class HttpInboundChannelAdapterParserTests { +public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTests { @Autowired private PollableChannel requests; diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java index 8dfb3db725..9fa262e23a 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java @@ -39,6 +39,7 @@ import org.springframework.integration.Message; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.http.AbstractHttpInboundTests; import org.springframework.integration.support.MessageBuilder; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -54,7 +55,7 @@ import org.springframework.web.servlet.View; * @author Biju Kunjummen * @since 2.0 */ -public class HttpRequestHandlingControllerTests { +public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests { @Test public void sendOnly() throws Exception { diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java index 7a22860198..504b76ca1a 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java @@ -16,9 +16,9 @@ package org.springframework.integration.http.inbound; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import java.io.IOException; @@ -43,6 +43,7 @@ import org.springframework.integration.Message; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.http.AbstractHttpInboundTests; import org.springframework.integration.http.converter.SerializingHttpMessageConverter; import org.springframework.integration.support.MessageBuilder; import org.springframework.mock.web.MockHttpServletRequest; @@ -58,7 +59,7 @@ import org.springframework.util.SerializationUtils; * @author Biju Kunjummen * @since 2.0 */ -public class HttpRequestHandlingMessagingGatewayTests { +public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboundTests { @Test @SuppressWarnings("unchecked") diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java index 37784a121c..9be3fcbd6e 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java @@ -32,6 +32,7 @@ import org.springframework.integration.MessageChannel; import org.springframework.integration.MessagingException; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.http.AbstractHttpInboundTests; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.util.AntPathMatcher; @@ -45,7 +46,7 @@ import org.springframework.web.servlet.HandlerMapping; * @author Artem Bilan * @author Biju Kunjummen */ -public class HttpRequestHandlingMessagingGatewayWithPathMappingTests { +public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends AbstractHttpInboundTests { private static ExpressionParser PARSER = new SpelExpressionParser(); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java index 30a934672f..cb409a1636 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests.java @@ -38,6 +38,7 @@ import org.springframework.integration.MessageHeaders; import org.springframework.integration.MessagingException; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.SubscribableChannel; +import org.springframework.integration.http.AbstractHttpInboundTests; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.ContextConfiguration; @@ -58,7 +59,7 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; //INT-2312 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class Int2312RequestMappingIntegrationTests { +public class Int2312RequestMappingIntegrationTests extends AbstractHttpInboundTests { public static final String TEST_PATH = "/test/{value}"; @@ -149,6 +150,8 @@ public class Int2312RequestMappingIntegrationTests { this.handlerAdapter.handle(request, response, handler); final String testResponse = response.getContentAsString(); assertEquals(testRequest.toLowerCase(), testResponse); + + RequestContextHolder.resetRequestAttributes(); } @Test From a5d4e3ab48e5bb1918cfa2cec28cc1962e6c17de Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 29 Oct 2013 20:50:20 +0200 Subject: [PATCH 09/12] INT-3181: Improve JdbcChannelMS usingIdCache doc JIRA: https://jira.springsource.org/browse/INT-3181 INT-3181: Fix JdbcChannelMS#pollMessageFromGroup * Add `doRemoveMessageFromGroup` and check its result. If it is `false` then just return `null`, not polled `Message` * Add `AbstractTxTimeoutMessageStoreTests#testInt3181ConcurrentPolling` test and implement it for some DBs * Make a note regarding MVCC as important in the Doc Minor Polishing --- .../jdbc/store/JdbcChannelMessageStore.java | 23 +++++++---- .../AbstractTxTimeoutMessageStoreTests.java | 30 +++++++++++++- .../DerbyTxTimeoutMessageStoreTests.java | 7 ++++ .../HsqlTxTimeoutMessageStoreTests.java | 7 ++++ .../MySqlTxTimeoutMessageStoreTests.java | 7 ++++ .../TxTimeoutMessageStoreTests-context.xml | 39 +++++++++++++++++++ src/reference/docbook/jdbc.xml | 9 +++++ 7 files changed, 113 insertions(+), 9 deletions(-) diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java index 7e0e655f6d..51cd0cdb20 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java @@ -16,7 +16,6 @@ package org.springframework.integration.jdbc.store; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -24,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -33,6 +31,7 @@ import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.serializer.Deserializer; @@ -42,12 +41,12 @@ import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.integration.Message; import org.springframework.integration.MessageHeaders; import org.springframework.integration.jdbc.JdbcMessageStore; +import org.springframework.integration.jdbc.store.channel.ChannelMessageStoreQueryProvider; import org.springframework.integration.jdbc.store.channel.DerbyChannelMessageStoreQueryProvider; import org.springframework.integration.jdbc.store.channel.MessageRowMapper; import org.springframework.integration.jdbc.store.channel.MySqlChannelMessageStoreQueryProvider; import org.springframework.integration.jdbc.store.channel.OracleChannelMessageStoreQueryProvider; import org.springframework.integration.jdbc.store.channel.PostgresChannelMessageStoreQueryProvider; -import org.springframework.integration.jdbc.store.channel.ChannelMessageStoreQueryProvider; import org.springframework.integration.store.AbstractMessageGroupStore; import org.springframework.integration.store.MessageGroup; import org.springframework.integration.store.MessageGroupStore; @@ -592,7 +591,9 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement final Message polledMessage = this.doPollForMessage(key); if (polledMessage != null){ - this.removeMessageFromGroup(groupId, polledMessage); + if (!this.doRemoveMessageFromGroup(groupId, polledMessage)) { + return null; + } } return polledMessage; @@ -607,18 +608,26 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement */ public MessageGroup removeMessageFromGroup(Object groupId, Message messageToRemove) { + this.doRemoveMessageFromGroup(groupId, messageToRemove); + + return getMessageGroup(groupId); + } + + private boolean doRemoveMessageFromGroup(Object groupId, Message messageToRemove) { final UUID id = messageToRemove.getHeaders().getId(); int updated = jdbcTemplate.update(getQuery(channelMessageStoreQueryProvider.getDeleteMessageQuery()), new Object[] { getKey(id), getKey(groupId), region }, new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR }); - if (updated != 0) { + boolean result = updated != 0; + if (result) { logger.debug(String.format("Message with id '%s' was deleted.", id)); - } else { + } + else { logger.warn(String.format("Message with id '%s' was not deleted.", id)); } - return getMessageGroup(groupId); + return result; } /** diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java index 712b5bff52..0982d30416 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java @@ -12,22 +12,27 @@ */ package org.springframework.integration.jdbc.store.channel; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import javax.sql.DataSource; +import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Assert; +import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.junit.Assert; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.jdbc.store.JdbcChannelMessageStore; @@ -62,8 +67,19 @@ abstract class AbstractTxTimeoutMessageStoreTests { protected TestService testService; @Autowired + @Qualifier("store") protected JdbcChannelMessageStore jdbcChannelMessageStore; + @Autowired + private MessageChannel first; + + @Autowired + private CountDownLatch successfulLatch; + + @Autowired + private AtomicInteger errorAtomicInteger; + + public void test() throws InterruptedException { int maxMessages = 10; @@ -157,4 +173,14 @@ abstract class AbstractTxTimeoutMessageStoreTests { assertTrue(executorService.awaitTermination(5, TimeUnit.SECONDS)); } + public void testInt3181ConcurrentPolling() throws InterruptedException { + for (int i = 0; i < 10; i++) { + this.first.send(new GenericMessage("test")); + } + + assertTrue(this.successfulLatch.await(5, TimeUnit.SECONDS)); + + assertEquals(0, errorAtomicInteger.get()); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java index f48528226d..b954331242 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java @@ -15,6 +15,7 @@ package org.springframework.integration.jdbc.store.channel; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -36,4 +37,10 @@ public class DerbyTxTimeoutMessageStoreTests extends AbstractTxTimeoutMessageSto super.test(); } + @Test + @Override + public void testInt3181ConcurrentPolling() throws InterruptedException { + super.testInt3181ConcurrentPolling(); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java index 803a31af9f..f88d4fec3d 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java @@ -16,6 +16,7 @@ import java.util.concurrent.ExecutionException; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -40,4 +41,10 @@ public class HsqlTxTimeoutMessageStoreTests extends AbstractTxTimeoutMessageStor super.testInt2993IdCacheConcurrency(); } + @Test + @Override + public void testInt3181ConcurrentPolling() throws InterruptedException { + super.testInt3181ConcurrentPolling(); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java index b7f816d06f..7eaa27a7f7 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java @@ -15,6 +15,7 @@ package org.springframework.integration.jdbc.store.channel; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -35,4 +36,10 @@ public class MySqlTxTimeoutMessageStoreTests extends AbstractTxTimeoutMessageSto super.test(); } + @Test + @Override + public void testInt3181ConcurrentPolling() throws InterruptedException { + super.testInt3181ConcurrentPolling(); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml index b4f04f17f0..7276d88ed6 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml @@ -54,5 +54,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/reference/docbook/jdbc.xml b/src/reference/docbook/jdbc.xml index e01bed77ec..7c067aa6e9 100644 --- a/src/reference/docbook/jdbc.xml +++ b/src/reference/docbook/jdbc.xml @@ -416,6 +416,7 @@ to configure the associated Poller with a TaskExecutor reference. + Keep in mind, though, that if you use a JDBC backed Message Channel and you are planning on polling the channel and consequently the message @@ -426,6 +427,13 @@ threads, may not materialize as expected. For example Apache Derby is problematic in that regard. + + To achieve better JDBC queue throughput, and avoid issues when different threads may poll the same + Message from the queue, it is important + to set the usingIdCache property of JdbcChannelMessageStore to true + when using databases that do not support MVCC: + + @@ -459,6 +467,7 @@ …]]> +
Initializing the Database From 848f3a51032c350b76f701273a61dbef6995bbfd Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Wed, 30 Oct 2013 18:57:58 +0200 Subject: [PATCH 10/12] INT-3185 Fix Direct Handlers When Exposed as MBean When determining candidates for direct invocation, we need to use the target of any existing proxy (such as JMX Metrics). Copy ServiceActivatorDefaultFrameworkMethodTests to the JMX project and adjust to work with JMX Proxies. INT-3185: fix for AbstractSMHFBean#checkReuse JIRA: https://jira.springsource.org/browse/INT-3185 --- ...ractStandardMessageHandlerFactoryBean.java | 14 +- ...eActivatorDefaultFrameworkMethodTests.java | 2 +- ...torDefaultFrameworkMethodTests-context.xml | 65 ++++++ ...eActivatorDefaultFrameworkMethodTests.java | 202 ++++++++++++++++++ 4 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests-context.xml create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java index 2c4a3f1b8b..a6b0605532 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java @@ -77,10 +77,10 @@ abstract class AbstractStandardMessageHandlerFactoryBean extends AbstractSimpleM if (this.targetObject != null) { Assert.state(this.expression == null, "The 'targetObject' and 'expression' properties are mutually exclusive."); - boolean targetIsDirectReplyProducingHandler = this.extractTypeIfPossible(targetObject, - AbstractReplyProducingMessageHandler.class) != null - && this.canBeUsedDirect( - (AbstractReplyProducingMessageHandler) targetObject) // give subclasses a say + AbstractReplyProducingMessageHandler actualHandler = this.extractTypeIfPossible(targetObject, + AbstractReplyProducingMessageHandler.class); + boolean targetIsDirectReplyProducingHandler = actualHandler != null + && this.canBeUsedDirect(actualHandler) // give subclasses a say && this.methodIsHandleMessageOrEmpty(this.targetMethodName); if (this.targetObject instanceof MessageProcessor) { handler = this.createMessageProcessingHandler((MessageProcessor) this.targetObject); @@ -89,9 +89,9 @@ abstract class AbstractStandardMessageHandlerFactoryBean extends AbstractSimpleM if (logger.isDebugEnabled()) { logger.debug("Wiring handler (" + beanName + ") directly into endpoint"); } + this.checkReuse(actualHandler); + this.postProcessReplyProducer(actualHandler); handler = (MessageHandler) targetObject; - this.checkReuse((AbstractReplyProducingMessageHandler) handler); - this.postProcessReplyProducer((AbstractReplyProducingMessageHandler) handler); } else { handler = this.createMethodInvokingHandler(this.targetObject, this.targetMethodName); @@ -120,7 +120,7 @@ abstract class AbstractStandardMessageHandlerFactoryBean extends AbstractSimpleM } private void checkReuse(AbstractReplyProducingMessageHandler replyHandler) { - Assert.isTrue(!referencedReplyProducers.contains(targetObject), + Assert.isTrue(!referencedReplyProducers.contains(replyHandler), "An AbstractReplyProducingMessageHandler may only be referenced once (" + replyHandler.getComponentName() + ") - use scope=\"prototype\""); referencedReplyProducers.add(replyHandler); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java index 152b41f15e..4756857945 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java @@ -94,7 +94,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests { } @Test - public void testNotOptimizedReplyingMessageHandler() { + public void testOptimizedReplyingMessageHandler() { QueueChannel replyChannel = new QueueChannel(); Message message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build(); this.optimizedRefReplyingHandlerTestInputChannel.send(message); diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests-context.xml new file mode 100644 index 0000000000..a222fe42dc --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests-context.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java new file mode 100644 index 0000000000..90ad33084d --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java @@ -0,0 +1,202 @@ +/* + * Copyright 2002-2013 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.jmx; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +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.channel.QueueChannel; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * See INT-1688 for background. + * + * @author Mark Fisher + * @author Artem Bilan + * @author Gary Russell + * @since 2.0.1 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class ServiceActivatorDefaultFrameworkMethodTests { + + @Autowired + private MessageChannel gatewayTestInputChannel; + + @Autowired + private MessageChannel replyingHandlerTestInputChannel; + + @Autowired + private MessageChannel optimizedRefReplyingHandlerTestInputChannel; + + @Autowired + private MessageChannel replyingHandlerWithStandardMethodTestInputChannel; + + @Autowired + private MessageChannel replyingHandlerWithOtherMethodTestInputChannel; + + @Autowired + private MessageChannel handlerTestInputChannel; + + @Autowired + private MessageChannel processorTestInputChannel; + + @Autowired + private EventDrivenConsumer processorTestService; + + @Autowired + private MessageProcessor testMessageProcessor; + + @Test + public void testGateway() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build(); + this.gatewayTestInputChannel.send(message); + Message reply = replyChannel.receive(0); + assertEquals("gatewayTestInputChannel,gatewayTestService,gateway,requestChannel,bridge,replyChannel", reply.getHeaders().get("history").toString()); + } + + @Test + public void testReplyingMessageHandler() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build(); + this.replyingHandlerTestInputChannel.send(message); + Message reply = replyChannel.receive(0); + assertEquals("TEST", reply.getPayload()); + assertEquals("replyingHandlerTestInputChannel,replyingHandlerTestService", reply.getHeaders().get("history").toString()); + StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack"); + assertEquals("doDispatch", st[15].getMethodName()); // close to the metal + } + + @Test + public void testOptimizedReplyingMessageHandler() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build(); + this.optimizedRefReplyingHandlerTestInputChannel.send(message); + Message reply = replyChannel.receive(0); + assertEquals("TEST", reply.getPayload()); + assertEquals("optimizedRefReplyingHandlerTestInputChannel,optimizedRefReplyingHandlerTestService", + reply.getHeaders().get("history").toString()); + StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack"); + assertEquals("doDispatch", st[15].getMethodName()); + } + + @Test + public void testReplyingMessageHandlerWithStandardMethod() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build(); + this.replyingHandlerWithStandardMethodTestInputChannel.send(message); + Message reply = replyChannel.receive(0); + assertEquals("TEST", reply.getPayload()); + assertEquals("replyingHandlerWithStandardMethodTestInputChannel,replyingHandlerWithStandardMethodTestService", reply.getHeaders().get("history").toString()); + StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack"); + assertEquals("doDispatch", st[15].getMethodName()); // close to the metal + } + + @Test + public void testReplyingMessageHandlerWithOtherMethod() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build(); + this.replyingHandlerWithOtherMethodTestInputChannel.send(message); + Message reply = replyChannel.receive(0); + assertEquals("bar", reply.getPayload()); + assertEquals("replyingHandlerWithOtherMethodTestInputChannel,replyingHandlerWithOtherMethodTestService", reply.getHeaders().get("history").toString()); + } + + @Test + public void testMessageHandler() { + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build(); + this.handlerTestInputChannel.send(message); + } + +// INT-2399 + @Test + public void testMessageProcessor() { + Object processor = TestUtils.getPropertyValue(processorTestService, "handler.h.advised.targetSource.target.processor"); + assertSame(testMessageProcessor, processor); + + QueueChannel replyChannel = new QueueChannel(); + Message message = MessageBuilder.withPayload("bar").setReplyChannel(replyChannel).build(); + this.processorTestInputChannel.send(message); + Message reply = replyChannel.receive(0); + assertEquals("foo:bar", reply.getPayload()); + assertEquals("processorTestInputChannel,processorTestService", reply.getHeaders().get("history").toString()); + } + + private interface Foo { + + public String foo(String in); + + } + + @SuppressWarnings("unused") + private static class TestReplyingMessageHandler extends AbstractReplyProducingMessageHandler implements Foo { + + @Override + protected Object handleRequestMessage(Message requestMessage) { + Exception e = new RuntimeException(); + StackTraceElement[] st = e.getStackTrace(); + return MessageBuilder.withPayload(requestMessage.getPayload().toString().toUpperCase()) + .setHeader("callStack", st); + } + + public String foo(String in) { + return "bar"; + } + + } + + @SuppressWarnings("unused") + private static class TestMessageHandler implements MessageHandler { + + @Override + public void handleMessage(Message requestMessage) { + Exception e = new RuntimeException(); + StackTraceElement[] st = e.getStackTrace(); + assertEquals("doDispatch", st[28].getMethodName()); + } + } + + @SuppressWarnings("unused") + private static class TestMessageProcessor implements MessageProcessor { + + private String prefix; + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String processMessage(Message message) { + return prefix + ":" + message.getPayload(); + } + } + +} From 498e9aa70476f9fb1b86615f7f92722259a7ce70 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 28 Oct 2013 20:35:03 +0200 Subject: [PATCH 11/12] INT-2664: (S)FTP Inbound: Preserve File Timestamp JIRA: https://jira.springsource.org/browse/INT-2664 INT-2664: add `preserve-timestamp` attribute Doc Polishing --- ...RemoteFileInboundChannelAdapterParser.java | 1 + .../AbstractInboundFileSynchronizer.java | 27 ++++++++++++------- .../inbound/FtpInboundFileSynchronizer.java | 8 +++++- .../ftp/config/spring-integration-ftp-3.0.xsd | 12 ++++++++- ...boundChannelAdapterParserTests-context.xml | 19 ++++++------- .../FtpInboundChannelAdapterParserTests.java | 1 + ...oundRemoteFileSystemSynchronizerTests.java | 14 ++++++++++ .../inbound/SftpInboundFileSynchronizer.java | 5 ++++ .../config/spring-integration-sftp-3.0.xsd | 12 ++++++++- ...boundChannelAdapterParserTests-context.xml | 11 ++++---- .../InboundChannelAdapterParserTests.java | 1 + ...oundRemoteFileSystemSynchronizerTests.java | 15 +++++++++++ src/reference/docbook/whats-new.xml | 4 +++ 13 files changed, 103 insertions(+), 27 deletions(-) diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java index 811fa53248..38e14b2141 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java @@ -46,6 +46,7 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst // configure the InboundFileSynchronizer properties IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "remote-directory"); IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "delete-remote-files"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "preserve-timestamp"); String remoteFileSeparator = element.getAttribute("remote-file-separator"); synchronizerBuilder.addPropertyValue("remoteFileSeparator", remoteFileSeparator); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java index cb42377025..590f3092bc 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java @@ -19,7 +19,6 @@ package org.springframework.integration.file.remote.synchronizer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -50,6 +49,7 @@ import org.springframework.util.ObjectUtils; * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell + * @author Artem Bilan * @since 2.0 */ public abstract class AbstractInboundFileSynchronizer implements InboundFileSynchronizer, @@ -89,6 +89,12 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS */ private volatile boolean deleteRemoteFiles; + /** + * Should we transfer the remote file timestamp + * to the local file? By default this is false. + */ + private volatile boolean preserveTimestamp; + /** * Create a synchronizer with the {@link SessionFactory} used to acquire {@link Session} instances. */ @@ -127,6 +133,10 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS this.deleteRemoteFiles = deleteRemoteFiles; } + public void setPreserveTimestamp(boolean preserveTimestamp) { + this.preserveTimestamp = preserveTimestamp; + } + @Override public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { this.evaluationContext = evaluationContext; @@ -149,7 +159,7 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS Session session = null; try { session = this.sessionFactory.getSession(); - Assert.state(session != null, "failed to acquire a Session"); + Assert.notNull(session, "failed to acquire a Session"); F[] files = session.list(this.remoteDirectory); if (!ObjectUtils.isEmpty(files)) { Collection filteredFiles = this.filterFiles(files); @@ -192,7 +202,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS if (!localFile.exists()) { String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix; File tempFile = new File(tempFileName); - InputStream inputStream = null; FileOutputStream fileOutputStream = new FileOutputStream(tempFile); try { session.read(remoteFilePath, fileOutputStream); @@ -206,13 +215,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS } } finally { - try { - if (inputStream != null) { - inputStream.close(); - } - } - catch (Exception ignored1) { - } try { fileOutputStream.close(); } @@ -228,6 +230,9 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS } } } + if (this.preserveTimestamp) { + localFile.setLastModified(getModified(remoteFile)); + } } } @@ -242,4 +247,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS protected abstract String getFilename(F file); + protected abstract long getModified(F file); + } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java index a5b942cae6..1bcca21604 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -28,6 +28,7 @@ import org.springframework.integration.file.remote.synchronizer.AbstractInboundF * @author Iwein Fuld * @author Josh Long * @author Mark Fisher + * @author Artem Bilan * @since 2.0 */ public class FtpInboundFileSynchronizer extends AbstractInboundFileSynchronizer { @@ -50,4 +51,9 @@ public class FtpInboundFileSynchronizer extends AbstractInboundFileSynchronizer< return (file != null ? file.getName() : null); } + @Override + protected long getModified(FTPFile file) { + return file.getTimestamp().getTimeInMillis(); + } + } diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd index bfd362abb3..afaaa5e246 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd @@ -235,7 +235,7 @@ - + Specify whether to delete the remote source @@ -245,6 +245,16 @@ + + + + Specify whether to preserve the modified timestamp from the remote source + file on the local file after copying. + By default, the remote timestamp will NOT be + preserved. + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml index 6d2732ec76..4b6f73f98f 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml @@ -7,20 +7,21 @@ http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd"> - - + - + @@ -48,13 +49,13 @@ - + @@ -76,7 +77,7 @@ - + @@ -89,7 +90,7 @@ - + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java index 1d1a62082a..4d40e37900 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java @@ -77,6 +77,7 @@ public class FtpInboundChannelAdapterParserTests { FtpInboundFileSynchronizer fisync = (FtpInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer"); assertNotNull(TestUtils.getPropertyValue(fisync, "localFilenameGeneratorExpression")); + assertTrue(TestUtils.getPropertyValue(fisync, "preserveTimestamp", Boolean.class)); assertEquals(".foo", TestUtils.getPropertyValue(fisync, "temporaryFileSuffix", String.class)); String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator"); assertNotNull(remoteFileSeparator); diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java index 96bc62aef5..7106219858 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -30,10 +31,12 @@ import static org.mockito.Mockito.when; import java.io.File; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Test; import org.mockito.Mockito; @@ -51,6 +54,7 @@ import org.springframework.integration.ftp.session.AbstractFtpSessionFactory; * @author Oleg Zhurakousky * @author Gunnar Hillert * @author Gary Russell + * @author Artem Bilan * @since 2.0 */ public class FtpInboundRemoteFileSystemSynchronizerTests { @@ -80,6 +84,7 @@ public class FtpInboundRemoteFileSystemSynchronizerTests { ftpSessionFactory.setHost("foo.com"); FtpInboundFileSynchronizer synchronizer = spy(new FtpInboundFileSynchronizer(ftpSessionFactory)); synchronizer.setDeleteRemoteFiles(true); + synchronizer.setPreserveTimestamp(true); synchronizer.setRemoteDirectory("remote-test-dir"); synchronizer.setFilter(new FtpRegexPatternFileListFilter(".*\\.test$")); synchronizer.setIntegrationEvaluationContext(ExpressionUtils.createStandardEvaluationContext()); @@ -98,9 +103,15 @@ public class FtpInboundRemoteFileSystemSynchronizerTests { Message atestFile = ms.receive(); assertNotNull(atestFile); assertEquals("A.TEST.a", atestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message btestFile = ms.receive(); assertNotNull(btestFile); assertEquals("B.TEST.a", btestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message nothing = ms.receive(); assertNull(nothing); @@ -127,6 +138,9 @@ public class FtpInboundRemoteFileSystemSynchronizerTests { FTPFile file = new FTPFile(); file.setName(fileName); file.setType(FTPFile.FILE_TYPE); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, 1); + file.setTimestamp(calendar); ftpFiles.add(file); when(ftpClient.retrieveFile(Mockito.eq("remote-test-dir/" + fileName) , Mockito.any(OutputStream.class))).thenReturn(true); } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java index 1ee8246995..a6f177aa75 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java @@ -46,4 +46,9 @@ public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer return (file != null ? file.getFilename() : null); } + @Override + protected long getModified(LsEntry file) { + return (long) file.getAttrs().getMTime() * 1000; + } + } diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd index 15d09c624f..912fcc0683 100644 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd +++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd @@ -236,7 +236,7 @@ - + Specify whether to delete the remote source @@ -246,6 +246,16 @@ + + + + Specify whether to preserve the modified timestamp from the remote source + file on the local file after copying. + By default, the remote timestamp will NOT be + preserved. + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml index 3e461e1db5..e0ffc0b0dc 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml @@ -22,11 +22,11 @@ - + - + @@ -49,14 +49,15 @@ temporary-file-suffix=".bar" comparator="comparator" local-filter="acceptAllFilter" - delete-remote-files="${delete.remote.files}"> + delete-remote-files="${delete.remote.files}" + preserve-timestamp="true"> - + @@ -118,7 +119,7 @@ - + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java index fb60fafd47..69284fd1e5 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java @@ -82,6 +82,7 @@ public class InboundChannelAdapterParserTests { assertNotNull(comparator); SftpInboundFileSynchronizer synchronizer = (SftpInboundFileSynchronizer) TestUtils.getPropertyValue(source, "synchronizer"); assertNotNull(TestUtils.getPropertyValue(synchronizer, "localFilenameGeneratorExpression")); + assertTrue(TestUtils.getPropertyValue(synchronizer, "preserveTimestamp", Boolean.class)); String remoteFileSeparator = (String) TestUtils.getPropertyValue(synchronizer, "remoteFileSeparator"); assertEquals(".bar", TestUtils.getPropertyValue(synchronizer, "temporaryFileSuffix", String.class)); assertNotNull(remoteFileSeparator); diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java index dd8842efb7..c8fa674f48 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -29,8 +30,10 @@ import static org.mockito.Mockito.when; import java.io.File; import java.io.FileInputStream; +import java.util.Calendar; import java.util.Vector; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Test; @@ -49,6 +52,7 @@ import com.jcraft.jsch.SftpATTRS; * @author Oleg Zhurakousky * @author Gunnar Hillert * @author Gary Russell + * @author Artem Bilan * @since 2.0 */ public class SftpInboundRemoteFileSystemSynchronizerTests { @@ -81,6 +85,7 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { SftpInboundFileSynchronizer synchronizer = spy(new SftpInboundFileSynchronizer(ftpSessionFactory)); synchronizer.setDeleteRemoteFiles(true); + synchronizer.setPreserveTimestamp(true); synchronizer.setRemoteDirectory("remote-test-dir"); synchronizer.setFilter(new SftpRegexPatternFileListFilter(".*\\.test$")); synchronizer.setIntegrationEvaluationContext(ExpressionUtils.createStandardEvaluationContext()); @@ -93,9 +98,15 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { Message atestFile = ms.receive(); assertNotNull(atestFile); assertEquals("a.test", atestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message btestFile = ms.receive(); assertNotNull(btestFile); assertEquals("b.test", btestFile.getPayload().getName()); + // The test remote files are created with the current timestamp + 1 day. + assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis())); + Message nothing = ms.receive(); assertNull(nothing); @@ -120,6 +131,10 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { LsEntry lsEntry = mock(LsEntry.class); SftpATTRS attributes = mock(SftpATTRS.class); when(lsEntry.getAttrs()).thenReturn(attributes); + + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, 1); + when(lsEntry.getAttrs().getMTime()).thenReturn(new Long(calendar.getTimeInMillis() / 1000).intValue()); when(lsEntry.getFilename()).thenReturn(fileName); sftpEntries.add(lsEntry); when(channel.get("remote-test-dir/"+fileName)).thenReturn(new FileInputStream("remote-test-dir/" + fileName)); diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 289b0d090a..5cb22379b3 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -248,6 +248,10 @@ to be maintained across JVM executions, a custom filter that retains state, perhaps on the file system, can now be configured. + + Inbound Channel Adapters now support the preserve-timestamp attribute, which + sets the local file modified timestamp to the timestamp from the server (default false). + For more information, see and . From cc434db959cfc5449276e3970d76c7f19a7ed222 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Wed, 30 Oct 2013 22:52:15 -0400 Subject: [PATCH 12/12] Test Case Fixes for Latest SPR 4 Snapshot - Close child context before parent to publish child close event to parent - SpelRouter FB injection - Don't pass null Method to TransactionAttributeSource.getTransactionAttribute() --- .../config/xml/DelayerParserTests.java | 10 ++++++++-- .../core/MessageIdGenerationTests.java | 16 ++++++++-------- .../router/config/RouterWithMappingTests.java | 14 ++++++-------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java index 3a5cd1a42c..8e3ff3d63a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java @@ -23,16 +23,20 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.Ordered; import org.springframework.expression.Expression; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.handler.DelayHandler; import org.springframework.integration.test.util.TestUtils; @@ -48,6 +52,7 @@ import org.springframework.transaction.interceptor.TransactionInterceptor; * @author Mark Fisher * @author Artem Bilan * @author Gunnar Hillert + * @author Gary Russell * @since 1.0.3 */ @RunWith(SpringJUnit4ClassRunner.class) @@ -100,7 +105,7 @@ public class DelayerParserTests { } @Test //INT-2649 - public void transactionalSubElement() { + public void transactionalSubElement() throws Exception { Object endpoint = context.getBean("delayerWithTransactional"); DelayHandler delayHandler = TestUtils.getPropertyValue(endpoint, "handler", DelayHandler.class); List adviceChain = TestUtils.getPropertyValue(delayHandler, "delayedAdviceChain", List.class); @@ -109,7 +114,8 @@ public class DelayerParserTests { assertTrue(advice instanceof TransactionInterceptor); TransactionAttributeSource transactionAttributeSource = ((TransactionInterceptor) advice).getTransactionAttributeSource(); assertTrue(transactionAttributeSource instanceof MatchAlwaysTransactionAttributeSource); - TransactionDefinition definition = transactionAttributeSource.getTransactionAttribute(null, null); + Method method = MessageHandler.class.getMethod("handleMessage", Message.class); + TransactionDefinition definition = transactionAttributeSource.getTransactionAttribute(method, null); assertEquals(TransactionDefinition.PROPAGATION_REQUIRED, definition.getPropagationBehavior()); assertEquals(TransactionDefinition.ISOLATION_DEFAULT, definition.getIsolationLevel()); assertEquals(TransactionDefinition.TIMEOUT_DEFAULT, definition.getTimeout()); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java index 8ab80bad87..a2707f1959 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java @@ -39,12 +39,12 @@ import org.springframework.util.StopWatch; /** * @author Oleg Zhurakousky * @author Gunnar Hillert + * @author Gary Russell */ - public class MessageIdGenerationTests { @Test - public void testCustomIdGenerationWithParentRegistrar() throws Exception{ + public void testCustomIdGenerationWithParentRegistrar() throws Exception { ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context-withGenerator.xml", this.getClass()); ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"MessageIdGenerationTests-context.xml"}, this.getClass(), parent); @@ -58,7 +58,7 @@ public class MessageIdGenerationTests { } @Test - public void testCustomIdGenerationWithParentChileIndependentCreation() throws Exception{ + public void testCustomIdGenerationWithParentChildIndependentCreation() throws Exception { ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context-withGenerator.xml", this.getClass()); GenericXmlApplicationContext child = new GenericXmlApplicationContext(); child.load("classpath:/org/springframework/integration/core/MessageIdGenerationTests-context.xml"); @@ -89,7 +89,7 @@ public class MessageIdGenerationTests { } @Test - public void testCustomIdGenerationWithChildRegistrar() throws Exception{ + public void testCustomIdGenerationWithChildRegistrar() throws Exception { ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context.xml", this.getClass()); ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"MessageIdGenerationTests-context-withGenerator.xml"}, this.getClass(), parent); @@ -98,13 +98,13 @@ public class MessageIdGenerationTests { MessageChannel inputChannel = child.getBean("input", MessageChannel.class); inputChannel.send(new GenericMessage(0)); verify(idGenerator, atLeastOnce()).generateId(); - parent.close(); child.close(); + parent.close(); this.assertDestroy(); } @Test - public void testCustomIdGenerationWithChildRegistrarClosed() throws Exception{ + public void testCustomIdGenerationWithChildRegistrarClosed() throws Exception { ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context.xml", this.getClass()); ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"MessageIdGenerationTests-context-withGenerator.xml"}, this.getClass(), parent); @@ -120,7 +120,7 @@ public class MessageIdGenerationTests { // similar to the last test, but should not fail because child AC is closed before second child AC is started @Test - public void testCustomIdGenerationWithParentChildIndependentCreationChildrenRegistrarsOneAtTheTime() throws Exception{ + public void testCustomIdGenerationWithParentChildIndependentCreationChildrenRegistrarsOneAtTheTime() throws Exception { ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context.xml", this.getClass()); GenericXmlApplicationContext childA = new GenericXmlApplicationContext(); @@ -142,7 +142,7 @@ public class MessageIdGenerationTests { @Test @Ignore - public void performanceTest(){ + public void performanceTest() { int times = 1000000; StopWatch watch = new StopWatch(); watch.start(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java index 8a0bc215dd..e704885c22 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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. @@ -26,16 +26,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; -import org.springframework.integration.config.ConsumerEndpointFactoryBean; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.router.AbstractMappingMessageRouter; import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Fisher + * @author Gary Russell */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) @@ -43,10 +42,10 @@ public class RouterWithMappingTests { @Autowired private MessageChannel expressionRouter; - + @Autowired - @Qualifier("spelRouter") - private ConsumerEndpointFactoryBean spelRouter; + @Qualifier("spelRouter.handler") + private AbstractMappingMessageRouter spelRouterHandler; @Autowired private MessageChannel pojoRouter; @@ -87,8 +86,7 @@ public class RouterWithMappingTests { assertNull(fooChannelForExpression.receive(0)); assertNull(barChannelForExpression.receive(0)); // validate dynamics - AbstractMappingMessageRouter router = (AbstractMappingMessageRouter) TestUtils.getPropertyValue(spelRouter, "handler"); - router.setChannelMapping("baz", "fooChannelForExpression"); + spelRouterHandler.setChannelMapping("baz", "fooChannelForExpression"); expressionRouter.send(message3); assertNull(defaultChannelForExpression.receive(10)); assertNotNull(fooChannelForExpression.receive(10));