/* * Copyright 2014-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. * 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.amqp.rabbit.listener.adapter; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import org.junit.Before; import org.junit.Test; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException; import org.springframework.amqp.rabbit.test.MessageTestUtils; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.SimpleMessageConverter; import org.springframework.beans.factory.support.StaticListableBeanFactory; import org.springframework.messaging.Message; import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.ReflectionUtils; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; /** * @author Stephane Nicoll * @author Artem Bilan * @author Gary Russell */ public class MessagingMessageListenerAdapterTests { private final DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); private final SampleBean sample = new SampleBean(); @Before public void setup() { initializeFactory(factory); } @Test public void buildMessageWithStandardMessage() throws Exception { Message<String> result = MessageBuilder.withPayload("Response") .setHeader("foo", "bar") .setHeader(AmqpHeaders.TYPE, "msg_type") .setHeader(AmqpHeaders.REPLY_TO, "reply") .build(); Channel session = mock(Channel.class); MessagingMessageListenerAdapter listener = getSimpleInstance("echo", Message.class); org.springframework.amqp.core.Message replyMessage = listener.buildMessage(session, result); assertNotNull("reply should never be null", replyMessage); assertEquals("Response", new String(replyMessage.getBody())); assertEquals("type header not copied", "msg_type", replyMessage.getMessageProperties().getType()); assertEquals("replyTo header not copied", "reply", replyMessage.getMessageProperties().getReplyTo()); assertEquals("custom header not copied", "bar", replyMessage.getMessageProperties().getHeaders().get("foo")); } @Test public void exceptionInListener() { org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("foo"); Channel channel = mock(Channel.class); MessagingMessageListenerAdapter listener = getSimpleInstance("fail", String.class); try { listener.onMessage(message, channel); fail("Should have thrown an exception"); } catch (ListenerExecutionFailedException ex) { assertEquals(IllegalArgumentException.class, ex.getCause().getClass()); assertEquals("Expected test exception", ex.getCause().getMessage()); } catch (Exception ex) { fail("Should not have thrown an " + ex.getClass().getSimpleName()); } } @Test public void exceptionInListenerBadReturnExceptionSetting() { org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("foo"); Channel channel = mock(Channel.class); MessagingMessageListenerAdapter listener = getSimpleInstance("fail", true, String.class); try { listener.onMessage(message, channel); fail("Should have thrown an exception"); } catch (ListenerExecutionFailedException ex) { assertEquals(IllegalArgumentException.class, ex.getCause().getClass()); assertEquals("Expected test exception", ex.getCause().getMessage()); } catch (Exception ex) { fail("Should not have thrown an " + ex.getClass().getSimpleName()); } } @Test public void exceptionInMultiListenerReturnException() throws Exception { org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("foo"); Channel channel = mock(Channel.class); MessagingMessageListenerAdapter listener = getMultiInstance("fail", "failWithReturn", true, String.class, Integer.class); try { listener.onMessage(message, channel); fail("Should have thrown an exception"); } catch (ListenerExecutionFailedException ex) { assertEquals(IllegalArgumentException.class, ex.getCause().getClass()); assertEquals("Expected test exception", ex.getCause().getMessage()); } catch (Exception ex) { ex.printStackTrace(); fail("Should not have thrown an " + ex.getClass().getSimpleName()); } message = new SimpleMessageConverter().toMessage(42, new MessageProperties()); try { listener.onMessage(message, channel); fail("Should have thrown an exception"); } catch (ReplyFailureException ex) { assertThat(ex.getMessage(), containsString("Failed to send reply")); } catch (Exception ex) { fail("Should not have thrown an " + ex.getClass().getSimpleName()); } message.getMessageProperties().setReplyTo("foo/bar"); listener.onMessage(message, channel); verify(channel).basicPublish(eq("foo"), eq("bar"), eq(false), any(BasicProperties.class), any(byte[].class)); } @Test public void exceptionInInvocation() { org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("foo"); Channel channel = mock(Channel.class); MessagingMessageListenerAdapter listener = getSimpleInstance("wrongParam", Integer.class); try { listener.onMessage(message, channel); fail("Should have thrown an exception"); } catch (ListenerExecutionFailedException ex) { assertEquals(org.springframework.messaging.converter.MessageConversionException.class, ex.getCause().getClass()); } catch (Exception ex) { fail("Should not have thrown an " + ex.getClass().getSimpleName()); } } @Test public void genericMessageTest1() throws Exception { org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("\"foo\""); Channel channel = mock(Channel.class); MessagingMessageListenerAdapter listener = getSimpleInstance("withGenericMessageAnyType", Message.class); listener.setMessageConverter(new Jackson2JsonMessageConverter()); message.getMessageProperties().setContentType("application/json"); listener.onMessage(message, channel); assertEquals(String.class, this.sample.payload.getClass()); message = org.springframework.amqp.core.MessageBuilder .withBody("{ \"foo\" : \"bar\" }".getBytes()) .andProperties(message.getMessageProperties()) .build(); listener.onMessage(message, channel); assertEquals(LinkedHashMap.class, this.sample.payload.getClass()); } @Test public void genericMessageTest2() throws Exception { org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("{ \"foo\" : \"bar\" }"); Channel channel = mock(Channel.class); MessagingMessageListenerAdapter listener = getSimpleInstance("withGenericMessageFooType", Message.class); listener.setMessageConverter(new Jackson2JsonMessageConverter()); message.getMessageProperties().setContentType("application/json"); listener.onMessage(message, channel); assertEquals(Foo.class, this.sample.payload.getClass()); } @Test public void genericMessageTest3() throws Exception { org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("{ \"foo\" : \"bar\" }"); Channel channel = mock(Channel.class); MessagingMessageListenerAdapter listener = getSimpleInstance("withNonGenericMessage", Message.class); listener.setMessageConverter(new Jackson2JsonMessageConverter()); message.getMessageProperties().setContentType("application/json"); listener.onMessage(message, channel); assertEquals(LinkedHashMap.class, this.sample.payload.getClass()); } protected MessagingMessageListenerAdapter getSimpleInstance(String methodName, Class<?>... parameterTypes) { Method m = ReflectionUtils.findMethod(SampleBean.class, methodName, parameterTypes); return createInstance(m, false); } protected MessagingMessageListenerAdapter getSimpleInstance(String methodName, boolean returnExceptions, Class<?>... parameterTypes) { Method m = ReflectionUtils.findMethod(SampleBean.class, methodName, parameterTypes); return createInstance(m, returnExceptions); } protected MessagingMessageListenerAdapter createInstance(Method m, boolean returnExceptions) { MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter(null, m, returnExceptions, null); adapter.setHandlerMethod(new HandlerAdapter(factory.createInvocableHandlerMethod(sample, m))); return adapter; } protected MessagingMessageListenerAdapter getMultiInstance(String methodName1, String methodName2, boolean returnExceptions, Class<?> m1ParameterType, Class<?> m2ParameterType) { Method m1 = ReflectionUtils.findMethod(SampleBean.class, methodName1, m1ParameterType); Method m2 = ReflectionUtils.findMethod(SampleBean.class, methodName2, m2ParameterType); return createMultiInstance(m1, m2, returnExceptions); } protected MessagingMessageListenerAdapter createMultiInstance(Method m1, Method m2, boolean returnExceptions) { MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter(null, null, returnExceptions, null); List<InvocableHandlerMethod> methods = new ArrayList<>(); methods.add(this.factory.createInvocableHandlerMethod(sample, m1)); methods.add(this.factory.createInvocableHandlerMethod(sample, m2)); DelegatingInvocableHandler handler = new DelegatingInvocableHandler(methods, this.sample, null, null); adapter.setHandlerMethod(new HandlerAdapter(handler)); return adapter; } private void initializeFactory(DefaultMessageHandlerMethodFactory factory) { factory.setBeanFactory(new StaticListableBeanFactory()); factory.afterPropertiesSet(); } private static class SampleBean { private Object payload; SampleBean() { super(); } @SuppressWarnings("unused") public Message<String> echo(Message<String> input) { return MessageBuilder.withPayload(input.getPayload()) .setHeader(AmqpHeaders.TYPE, "reply") .build(); } @SuppressWarnings("unused") public void fail(String input) { throw new IllegalArgumentException("Expected test exception"); } @SuppressWarnings("unused") public void wrongParam(Integer i) { throw new IllegalArgumentException("Should not have been called"); } @SuppressWarnings("unused") public void withGenericMessageAnyType(Message<?> message) { this.payload = message.getPayload(); } @SuppressWarnings("unused") public void withFoo(Foo foo) { this.payload = foo; } @SuppressWarnings("unused") public void withGenericMessageFooType(Message<Foo> message) { this.payload = message.getPayload(); } @SuppressWarnings("unused") public void withNonGenericMessage(@SuppressWarnings("rawtypes") Message message) { this.payload = message.getPayload(); } @SuppressWarnings("unused") public String failWithReturn(Integer input) { throw new IllegalArgumentException("Expected test exception"); } } private static class Foo { private String foo; @SuppressWarnings("unused") public String getFoo() { return this.foo; } @SuppressWarnings("unused") public void setFoo(String foo) { this.foo = foo; } } }