From d8fc94602c1eb7219d6286b98e1904b7a6caa1c9 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Sat, 5 Sep 2015 00:18:42 -0400 Subject: [PATCH] INT-3820: Fix `MethodInvokerHelper` for the Proxy JIRA: https://jira.spring.io/browse/INT-3820 Some Proxy may be based on the specif non-user classes and advised with the provided end-user interfaces. The best sample is `@Repository` from Spring Data projects. In this case the `MessagingMethodInvokerHelper` just missed the user interfaces to consider for the candidate methods or thrown an `Exception` like `NoSuchMethodError`. * Add `((Advised) targetObject).getProxiedInterfaces()` for the scanning algorithm * Move the `UniqueMethodFilter` to the internal `Set` to allow iteration and filtering for methods from the `targetClass` as well as from all those user interfaces * Add Spring JPA repository test-case * Polishing form some time-weak tests Revert MessagingMethodInvokerHelper INT-3820: Simple Proxy Interface Method Matching No-risk solution for INT-3820. Instead of considering all interfaces on the proxy, only look at proxy interfaces for a single matching method name if we find no candidate or fallback methods. Consider the complete solution for 4.3. Add `Message` method distinguishing --- build.gradle | 3 + .../util/MessagingMethodInvokerHelper.java | 62 +++++++++++++++++++ .../DispatchingChannelErrorHandlingTests.java | 6 +- .../config/xml/GatewayParserTests.java | 2 +- .../xml/PollerWithErrorChannelTests.java | 27 ++++---- .../handler/DelayHandlerTests.java | 6 +- .../MethodInvokingMessageProcessorTests.java | 2 +- ...actPersistentAcceptOnceFileListFilter.java | 2 +- .../jms/OutboundGatewayFunctionTests.java | 3 + ...nTreePollingChannelAdapterParserTests.java | 7 +-- .../JpaOutboundGatewayTests-context.xml | 23 ++++--- .../jpa/outbound/JpaOutboundGatewayTests.java | 6 ++ .../jpa/outbound/StudentRepository.java | 18 ++++++ .../jpa/outbound/StudentService.java | 3 + ...acterStreamWritingMessageHandlerTests.java | 6 +- 15 files changed, 139 insertions(+), 37 deletions(-) create mode 100644 spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentRepository.java diff --git a/build.gradle b/build.gradle index 43caca0644..7a22639466 100644 --- a/build.gradle +++ b/build.gradle @@ -133,6 +133,7 @@ subprojects { subproject -> smackVersion = '4.0.6' springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '1.5.0.RELEASE' // springCloudClusterVersion = '1.0.0.BUILD-SNAPSHOT' + springDataJpaVersion = '1.8.2.RELEASE' springDataMongoVersion = '1.7.2.RELEASE' springDataRedisVersion = '1.5.2.RELEASE' springGemfireVersion = '1.6.0.RELEASE' @@ -452,6 +453,8 @@ project('spring-integration-jpa') { compile ("org.eclipse.persistence:javax.persistence:$jpaApiVersion", optional) + testCompile "org.springframework.data:spring-data-jpa:$springDataJpaVersion" + testCompile "com.h2database:h2:$h2Version" testCompile "org.hsqldb:hsqldb:$hsqldbVersion" testCompile "org.apache.derby:derby:$derbyVersion" diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java index 1d6ea38f21..9ca108ecb3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java @@ -440,6 +440,12 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } }, methodFilter); + if (candidateMethods.isEmpty() && candidateMessageMethods.isEmpty() && fallbackMethods.isEmpty() + && fallbackMessageMethods.isEmpty()) { + findSingleSpecifMethodOnInterfacesIfProxy(targetObject, methodName, candidateMessageMethods, + candidateMethods); + } + if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) { handlerMethods.put(CANDIDATE_METHODS, candidateMethods); handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods); @@ -499,6 +505,62 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator return handlerMethods; } + private void findSingleSpecifMethodOnInterfacesIfProxy(final Object targetObject, final String methodName, + Map, HandlerMethod> candidateMessageMethods, + Map, HandlerMethod> candidateMethods) { + if (AopUtils.isAopProxy(targetObject)) { + final AtomicReference targetMethod = new AtomicReference(); + Class[] interfaces = ((Advised) targetObject).getProxiedInterfaces(); + for (Class clazz : interfaces) { + ReflectionUtils.doWithMethods(clazz, new MethodCallback() { + + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + if (targetMethod.get() != null) { + throw new IllegalStateException("Ambiguous method " + methodName + " on " + targetObject); + } + else { + targetMethod.set(method); + } + } + + }, new MethodFilter() { + + @Override + public boolean matches(Method method) { + return method.getName().equals(methodName); + } + + }); + } + Method method = targetMethod.get(); + if (method != null) { + HandlerMethod handlerMethod = new HandlerMethod(method, this.canProcessMessageList); + Class targetParameterType = handlerMethod.getTargetParameterType(); + if (handlerMethod.isMessageMethod()) { + if (candidateMessageMethods.containsKey(targetParameterType)) { + throw new IllegalArgumentException("Found more than one method match for type " + + "[Message<" + targetParameterType + ">]"); + } + candidateMessageMethods.put(targetParameterType, handlerMethod); + } + else { + if (candidateMethods.containsKey(targetParameterType)) { + String exceptionMessage = "Found more than one method match for "; + if (Void.class.equals(targetParameterType)) { + exceptionMessage += "empty parameter for 'payload'"; + } + else { + exceptionMessage += "type [" + targetParameterType + "]"; + } + throw new IllegalArgumentException(exceptionMessage); + } + candidateMethods.put(targetParameterType, handlerMethod); + } + } + } + } + private Class getTargetClass(Object targetObject) { Class targetClass = targetObject.getClass(); if (AopUtils.isAopProxy(targetObject)) { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/DispatchingChannelErrorHandlingTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/DispatchingChannelErrorHandlingTests.java index 93ec381413..2f63e80c8c 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/channel/DispatchingChannelErrorHandlingTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/DispatchingChannelErrorHandlingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2015 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. @@ -77,7 +77,7 @@ public class DispatchingChannelErrorHandlingTests { }); Message message = MessageBuilder.withPayload("test").build(); channel.send(message); - this.waitForLatch(1000); + this.waitForLatch(10000); Message errorMessage = resultHandler.lastMessage; assertEquals(MessagingException.class, errorMessage.getPayload().getClass()); MessagingException exceptionPayload = (MessagingException) errorMessage.getPayload(); @@ -108,7 +108,7 @@ public class DispatchingChannelErrorHandlingTests { }); Message message = MessageBuilder.withPayload("test").build(); channel.send(message); - this.waitForLatch(1000); + this.waitForLatch(10000); Message errorMessage = resultHandler.lastMessage; assertEquals(MessagingException.class, errorMessage.getPayload().getClass()); MessagingException exceptionPayload = (MessagingException) errorMessage.getPayload(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java index 83dff72aa0..408ef1cb10 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java @@ -386,7 +386,7 @@ public class GatewayParserTests { public Future submit(Callable task) { try { Future result = super.submit(task); - Message message = (Message) result.get(1, TimeUnit.SECONDS); + Message message = (Message) result.get(10, TimeUnit.SECONDS); Message modifiedMessage; if (message == null) { modifiedMessage = MessageBuilder.withPayload("foo") diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerWithErrorChannelTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerWithErrorChannelTests.java index d2008011d5..5327da280f 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerWithErrorChannelTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerWithErrorChannelTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -47,7 +47,7 @@ public class PollerWithErrorChannelTests { * the ErrorMessage will still be forwarded to the 'errorChannel' since exception occurs on * receive() and not on send() */ - public void testWithErrorChannelAsHeader() throws Exception{ + public void testWithErrorChannelAsHeader() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass()); SourcePollingChannelAdapter adapter = ac.getBean("withErrorHeader", SourcePollingChannelAdapter.class); @@ -61,49 +61,52 @@ public class PollerWithErrorChannelTests { } @Test - public void testWithErrorChannel() throws Exception{ + public void testWithErrorChannel() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass()); SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannel", SourcePollingChannelAdapter.class); adapter.start(); PollableChannel errorChannel = ac.getBean("eChannel", PollableChannel.class); - assertNotNull(errorChannel.receive(1000)); + assertNotNull(errorChannel.receive(10000)); adapter.stop(); } @Test - public void testWithErrorChannelAndHeader() throws Exception{ + public void testWithErrorChannelAndHeader() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass()); SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannelAndHeader", SourcePollingChannelAdapter.class); adapter.start(); PollableChannel errorChannel = ac.getBean("eChannel", PollableChannel.class); - assertNotNull(errorChannel.receive(1000)); + assertNotNull(errorChannel.receive(10000)); adapter.stop(); } @Test // config the same as above but the error wil come from the send - public void testWithErrorChannelAndHeaderWithSendFailure() throws Exception{ + public void testWithErrorChannelAndHeaderWithSendFailure() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass()); SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannelAndHeaderErrorOnSend", SourcePollingChannelAdapter.class); adapter.start(); PollableChannel errorChannel = ac.getBean("errChannel", PollableChannel.class); - assertNotNull(errorChannel.receive(1000)); + assertNotNull(errorChannel.receive(10000)); adapter.stop(); } @Test // INT-1952 - public void testWithErrorChannelAndPollingConsumer() throws Exception{ + public void testWithErrorChannelAndPollingConsumer() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml", this.getClass()); MessageChannel serviceWithPollerChannel = ac.getBean("serviceWithPollerChannel", MessageChannel.class); - QueueChannel errChannel = ac.getBean("serviceErrorChannel", QueueChannel.class); + QueueChannel errorChannel = ac.getBean("serviceErrorChannel", QueueChannel.class); serviceWithPollerChannel.send(new GenericMessage("")); - assertNotNull(errChannel.receive(1000)); + assertNotNull(errorChannel.receive(10000)); } - public static class SampleService{ + public static class SampleService { + public String withSuccess(){ return "hello"; } + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java index ffe91a10d3..ecd37cd813 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java @@ -243,6 +243,7 @@ public class DelayHandlerTests { final CountDownLatch latch = new CountDownLatch(1); new Thread(new Runnable() { + @Override public void run() { try { @@ -253,9 +254,10 @@ public class DelayHandlerTests { // won't countDown } } + }).start(); - latch.await(50, TimeUnit.MILLISECONDS); - assertEquals(0, latch.getCount()); + + assertTrue(latch.await(1, TimeUnit.SECONDS)); } @Test diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java index 63cfb09c49..4538223245 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java index fb3e688f21..5b363a3b82 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java @@ -63,7 +63,7 @@ public abstract class AbstractPersistentAcceptOnceFileListFilter extends Abst /** * Determine whether the metadataStore should be flushed on each update (if {@link Flushable}). * @param flushOnUpdate true to flush. - * @since 1.4.5 + * @since 4.1.5 */ public void setFlushOnUpdate(boolean flushOnUpdate) { this.flushOnUpdate = flushOnUpdate; diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java b/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java index 61bc04e52e..b9d63a8af3 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/OutboundGatewayFunctionTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.jms; import static org.junit.Assert.assertFalse; @@ -401,6 +402,8 @@ public class OutboundGatewayFunctionTests { gateway.setCorrelationKey("JMSCorrelationID"); gateway.setUseReplyContainer(true); gateway.setIdleReplyContainerTimeout(1, TimeUnit.SECONDS); + gateway.setRequiresReply(true); + gateway.setReceiveTimeout(10000); gateway.afterPropertiesSet(); gateway.start(); Executors.newSingleThreadExecutor().execute(new Runnable() { diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests.java index 5ad540de8a..bda149aeb4 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.jmx.config; import static org.junit.Assert.assertEquals; @@ -24,7 +25,6 @@ import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.Map; -import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.QueryExp; @@ -103,10 +103,7 @@ public class MBeanTreePollingChannelAdapterParserTests { @Autowired private MBeanObjectConverter converter; - @Autowired - private MBeanServer mbeanServer; - - private final long testTimeout = 2000L; + private final long testTimeout = 20000L; @Test public void pollDefaultAdapter() throws Exception { diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml index 7184d17310..7084b67f20 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml @@ -1,19 +1,21 @@ + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> + + + + @@ -33,6 +35,7 @@ + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java index 0b7d48cb4d..cdcef19f5b 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java @@ -169,4 +169,10 @@ public class JpaOutboundGatewayTests { } + @Test + public void testJpaRepositoryAsService() { + List students = this.studentService.getStudentsUsingJpaRepository("F"); + Assert.assertEquals(2, students.size()); + } + } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentRepository.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentRepository.java new file mode 100644 index 0000000000..c8ee6342cd --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentRepository.java @@ -0,0 +1,18 @@ +package org.springframework.integration.jpa.outbound; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.integration.jpa.test.entity.StudentDomain; +import org.springframework.stereotype.Repository; + +/** + * @author Artem Bilan + * @since 4.2 + */ +@Repository +public interface StudentRepository extends JpaRepository { + + List findByGender(String gender); + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java index 44f7245752..bd738be1e4 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java @@ -52,4 +52,7 @@ public interface StudentService { StudentDomain persistStudent2(StudentDomain studentToPersist); List getStudents(int maxNumberOfRecords); + + List getStudentsUsingJpaRepository(String gender); + } diff --git a/spring-integration-stream/src/test/java/org/springframework/integration/stream/CharacterStreamWritingMessageHandlerTests.java b/spring-integration-stream/src/test/java/org/springframework/integration/stream/CharacterStreamWritingMessageHandlerTests.java index 6bcd8d9643..bb30a05661 100644 --- a/spring-integration-stream/src/test/java/org/springframework/integration/stream/CharacterStreamWritingMessageHandlerTests.java +++ b/spring-integration-stream/src/test/java/org/springframework/integration/stream/CharacterStreamWritingMessageHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2015 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. @@ -201,6 +201,7 @@ public class CharacterStreamWritingMessageHandlerTests { public String toString() { return this.text; } + } @@ -225,7 +226,7 @@ public class CharacterStreamWritingMessageHandlerTests { public void await() { try { - this.latch.await(1000, TimeUnit.MILLISECONDS); + this.latch.await(10000, TimeUnit.MILLISECONDS); if (latch.getCount() != 0) { throw new RuntimeException("test timeout"); } @@ -234,6 +235,7 @@ public class CharacterStreamWritingMessageHandlerTests { throw new RuntimeException("test latch.await() interrupted"); } } + } }