From 8230a8b3485d07a93707f099c2b97b6227b29a5e Mon Sep 17 00:00:00 2001 From: Ilayaperumal Gopinathan Date: Thu, 9 Mar 2017 20:09:39 +0530 Subject: [PATCH] Fix StreamListener method with no explicit declarative method params - Treat methods with Object arguments as 'handler' as opposed to 'declarative' - Add test Resolves #844 Check object type to decide handler method --- .../StreamListenerHandlerMethodTests.java | 42 +++++++++++++++---- ...amListenerAnnotationBeanPostProcessor.java | 11 ++--- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java index c21b4e150..5bb7a2647 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -32,6 +32,7 @@ import org.springframework.cloud.stream.annotation.Output; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Processor; import org.springframework.cloud.stream.messaging.Sink; +import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; @@ -40,6 +41,7 @@ import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.util.Assert; import static org.assertj.core.api.Assertions.assertThat; @@ -64,18 +66,33 @@ public class StreamListenerHandlerMethodTests { public void testInvalidInputOnMethod() throws Exception { try { SpringApplication.run(TestInvalidInputOnMethod.class, "--server.port=0"); - fail("Exception expected: "+ INPUT_AT_STREAM_LISTENER); + fail("Exception expected: " + INPUT_AT_STREAM_LISTENER); } catch (BeanCreationException e) { assertThat(e.getCause().getMessage()).contains(INPUT_AT_STREAM_LISTENER); } } + @Test + public void testMethodWithObjectAsMethodArgument() throws Exception { + ConfigurableApplicationContext context = SpringApplication.run(TestMethodWithObjectAsMethodArgument.class, "--server.port=0"); + Processor processor = context.getBean(Processor.class); + String id = UUID.randomUUID().toString(); + final CountDownLatch latch = new CountDownLatch(1); + final String testMessage = "testing"; + processor.input().send(MessageBuilder.withPayload(testMessage).build()); + MessageCollector messageCollector = context.getBean(MessageCollector.class); + Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + assertThat(result).isNotNull(); + assertThat(result.getPayload()).isEqualTo(result.getPayload().toString().toUpperCase()); + context.close(); + } + @Test public void testInvalidReturnTypeWithSendToAndOutput() throws Exception { try { SpringApplication.run(TestReturnTypeWithMultipleOutput.class, "--server.port=0"); - fail("Exception expected: "+ RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED); + fail("Exception expected: " + RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED); } catch (BeanCreationException e) { assertThat(e.getCause().getMessage()).contains(RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED); @@ -97,7 +114,7 @@ public class StreamListenerHandlerMethodTests { public void testInvalidInputAnnotationWithNoValue() throws Exception { try { SpringApplication.run(TestInvalidInputAnnotationWithNoValue.class, "--server.port=0"); - fail("Exception expected: "+ INVALID_INBOUND_NAME); + fail("Exception expected: " + INVALID_INBOUND_NAME); } catch (BeanCreationException e) { assertThat(e.getCause().getMessage()).contains(INVALID_INBOUND_NAME); @@ -108,7 +125,7 @@ public class StreamListenerHandlerMethodTests { public void testInvalidOutputAnnotationWithNoValue() throws Exception { try { SpringApplication.run(TestInvalidOutputAnnotationWithNoValue.class, "--server.port=0"); - fail("Exception expected: "+ INVALID_OUTBOUND_NAME); + fail("Exception expected: " + INVALID_OUTBOUND_NAME); } catch (BeanCreationException e) { assertThat(e.getCause().getMessage()).contains(INVALID_OUTBOUND_NAME); @@ -143,7 +160,7 @@ public class StreamListenerHandlerMethodTests { public void testAmbiguousMethodArguments1() throws Exception { try { SpringApplication.run(TestAmbiguousMethodArguments1.class, "--server.port=0"); - fail("Exception expected: "+ AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS); + fail("Exception expected: " + AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS); } catch (BeanCreationException e) { assertThat(e.getCause().getMessage()).contains(AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS); @@ -176,7 +193,7 @@ public class StreamListenerHandlerMethodTests { public void testMethodWithOutputAsMethodAndParameter() throws Exception { try { SpringApplication.run(TestMethodWithOutputAsMethodAndParameter.class, "--server.port=0"); - fail("Exception expected:" + INVALID_OUTPUT_VALUES); + fail("Exception expected:" + INVALID_OUTPUT_VALUES); } catch (BeanCreationException e) { assertThat(e.getCause().getMessage()).startsWith(INVALID_OUTPUT_VALUES); @@ -275,6 +292,17 @@ public class StreamListenerHandlerMethodTests { } } + @EnableBinding({Processor.class}) + @EnableAutoConfiguration + public static class TestMethodWithObjectAsMethodArgument { + + @StreamListener(Processor.INPUT) + @SendTo(Processor.OUTPUT) + public String receive(Object received) { + return received.toString().toUpperCase(); + } + } + @EnableBinding({Sink.class}) @EnableAutoConfiguration public static class TestInvalidInputOnMethod { diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java index 3d7e3d0ea..a21be23b0 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java @@ -71,7 +71,7 @@ import org.springframework.util.StringUtils; */ public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, BeanFactoryAware, SmartInitializingSingleton, - InitializingBean { + InitializingBean { private static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser(); @@ -100,7 +100,7 @@ public class StreamListenerAnnotationBeanPostProcessor private BeanExpressionContext expressionContext; @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"rawtypes", "unchecked"}) public final void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = (ConfigurableApplicationContext) applicationContext; } @@ -182,7 +182,7 @@ public class StreamListenerAnnotationBeanPostProcessor * the postprocessor. * * @param originalAnnotation the original annotation - * @param annotatedMethod the method on which the annotation has been found + * @param annotatedMethod the method on which the annotation has been found * @return the postprocessed {@link StreamListener} annotation */ protected StreamListener postProcessAnnotation(StreamListener originalAnnotation, Method annotatedMethod) { @@ -226,7 +226,8 @@ public class StreamListenerAnnotationBeanPostProcessor private boolean isDeclarativeMethodParameter(Object targetBean, MethodParameter methodParameter) { if (targetBean != null) { - if (methodParameter.getParameterType().isAssignableFrom(targetBean.getClass())) { + if (!methodParameter.getParameterType().equals(Object.class) + && methodParameter.getParameterType().isAssignableFrom(targetBean.getClass())) { return true; } for (StreamListenerParameterAdapter streamListenerParameterAdapter : this.streamListenerParameterAdapters) { @@ -238,7 +239,7 @@ public class StreamListenerAnnotationBeanPostProcessor return false; } - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"rawtypes", "unchecked"}) private void invokeSetupMethodOnListenedChannel(Method method, Object bean, String inboundName, String outboundName) { Object[] arguments = new Object[method.getParameterTypes().length];