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<Method>` 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
This commit is contained in:
committed by
Gary Russell
parent
ca62f18a7b
commit
d8fc94602c
@@ -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"
|
||||
|
||||
@@ -440,6 +440,12 @@ public class MessagingMethodInvokerHelper<T> 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<T> extends AbstractExpressionEvaluator
|
||||
return handlerMethods;
|
||||
}
|
||||
|
||||
private void findSingleSpecifMethodOnInterfacesIfProxy(final Object targetObject, final String methodName,
|
||||
Map<Class<?>, HandlerMethod> candidateMessageMethods,
|
||||
Map<Class<?>, HandlerMethod> candidateMethods) {
|
||||
if (AopUtils.isAopProxy(targetObject)) {
|
||||
final AtomicReference<Method> targetMethod = new AtomicReference<Method>();
|
||||
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)) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -386,7 +386,7 @@ public class GatewayParserTests {
|
||||
public <T> Future<T> submit(Callable<T> 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")
|
||||
|
||||
@@ -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<String>(""));
|
||||
assertNotNull(errChannel.receive(1000));
|
||||
assertNotNull(errorChannel.receive(10000));
|
||||
}
|
||||
|
||||
public static class SampleService{
|
||||
public static class SampleService {
|
||||
|
||||
public String withSuccess(){
|
||||
return "hello";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -63,7 +63,7 @@ public abstract class AbstractPersistentAcceptOnceFileListFilter<F> 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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:jpa="http://www.springframework.org/schema/integration/jpa"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:jpa="http://www.springframework.org/schema/integration/jpa"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:sd-jpa="http://www.springframework.org/schema/data/jpa"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd
|
||||
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
|
||||
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
||||
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
|
||||
|
||||
<sd-jpa:repositories base-package="org.springframework.integration.jpa.outbound"/>
|
||||
|
||||
<int:service-activator input-channel="jpaInputChannel" ref="studentRepository" method="findByGender"/>
|
||||
|
||||
<import resource="BaseJpaPollingChannelAdapterTests-context.xml"/>
|
||||
|
||||
@@ -33,6 +35,7 @@
|
||||
<int:method name="persistStudent2" request-channel="updatingGatewayInsideChain" />
|
||||
<int:method name="getAllStudentsFromGivenRecord" request-channel="getStudentsFromGivenRecordChannel"/>
|
||||
<int:method name="getStudents" request-channel="getStudentsWithMaxNumberOfRecordsChannel"/>
|
||||
<int:method name="getStudentsUsingJpaRepository" request-channel="jpaInputChannel"/>
|
||||
</int:gateway>
|
||||
|
||||
<int:channel id="studentReplyChannel"/>
|
||||
|
||||
@@ -169,4 +169,10 @@ public class JpaOutboundGatewayTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJpaRepositoryAsService() {
|
||||
List<StudentDomain> students = this.studentService.getStudentsUsingJpaRepository("F");
|
||||
Assert.assertEquals(2, students.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<StudentDomain, Long> {
|
||||
|
||||
List<StudentDomain> findByGender(String gender);
|
||||
|
||||
}
|
||||
@@ -52,4 +52,7 @@ public interface StudentService {
|
||||
StudentDomain persistStudent2(StudentDomain studentToPersist);
|
||||
|
||||
List<StudentDomain> getStudents(int maxNumberOfRecords);
|
||||
|
||||
List<StudentDomain> getStudentsUsingJpaRepository(String gender);
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user