/* * Copyright 2002-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.jms.config; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.jms.Destination; import javax.jms.InvalidDestinationException; import javax.jms.JMSException; import javax.jms.ObjectMessage; import javax.jms.QueueSender; import javax.jms.Session; import javax.jms.TextMessage; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TestName; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.support.StaticListableBeanFactory; import org.springframework.jms.StubTextMessage; import org.springframework.jms.listener.DefaultMessageListenerContainer; import org.springframework.jms.listener.MessageListenerContainer; import org.springframework.jms.listener.SimpleMessageListenerContainer; import org.springframework.jms.listener.adapter.ListenerExecutionFailedException; import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter; import org.springframework.jms.listener.adapter.ReplyFailureException; import org.springframework.jms.support.JmsHeaders; import org.springframework.jms.support.JmsMessageHeaderAccessor; import org.springframework.jms.support.QosSettings; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.destination.DestinationResolver; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MessageConversionException; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Headers; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; import org.springframework.util.ReflectionUtils; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.springframework.validation.annotation.Validated; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.mock; import static org.mockito.BDDMockito.verify; /** * @author Stephane Nicoll */ public class MethodJmsListenerEndpointTests { private final DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); private final DefaultMessageListenerContainer container = new DefaultMessageListenerContainer(); private final JmsEndpointSampleBean sample = new JmsEndpointSampleBean(); @Rule public final TestName name = new TestName(); @Rule public final ExpectedException thrown = ExpectedException.none(); @Before public void setup() { initializeFactory(this.factory); } @Test public void createMessageListenerNoFactory() { MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint(); endpoint.setBean(this); endpoint.setMethod(getTestMethod()); this.thrown.expect(IllegalStateException.class); endpoint.createMessageListener(this.container); } @Test public void createMessageListener() { MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint(); endpoint.setBean(this); endpoint.setMethod(getTestMethod()); endpoint.setMessageHandlerMethodFactory(this.factory); assertNotNull(endpoint.createMessageListener(this.container)); } @Test public void setExtraCollaborators() { MessageConverter messageConverter = mock(MessageConverter.class); DestinationResolver destinationResolver = mock(DestinationResolver.class); this.container.setMessageConverter(messageConverter); this.container.setDestinationResolver(destinationResolver); MessagingMessageListenerAdapter listener = createInstance(this.factory, getListenerMethod("resolveObjectPayload", MyBean.class), this.container); DirectFieldAccessor accessor = new DirectFieldAccessor(listener); assertSame(messageConverter, accessor.getPropertyValue("messageConverter")); assertSame(destinationResolver, accessor.getPropertyValue("destinationResolver")); } @Test public void resolveMessageAndSession() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(javax.jms.Message.class, Session.class); Session session = mock(Session.class); listener.onMessage(createSimpleJmsTextMessage("test"), session); assertDefaultListenerMethodInvocation(); } @Test public void resolveGenericMessage() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(Message.class); Session session = mock(Session.class); listener.onMessage(createSimpleJmsTextMessage("test"), session); assertDefaultListenerMethodInvocation(); } @Test public void resolveHeaderAndPayload() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, int.class); Session session = mock(Session.class); StubTextMessage message = createSimpleJmsTextMessage("my payload"); message.setIntProperty("myCounter", 55); listener.onMessage(message, session); assertDefaultListenerMethodInvocation(); } @Test public void resolveCustomHeaderNameAndPayload() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, int.class); Session session = mock(Session.class); StubTextMessage message = createSimpleJmsTextMessage("my payload"); message.setIntProperty("myCounter", 24); listener.onMessage(message, session); assertDefaultListenerMethodInvocation(); } @Test public void resolveCustomHeaderNameAndPayloadWithHeaderNameSet() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, int.class); Session session = mock(Session.class); StubTextMessage message = createSimpleJmsTextMessage("my payload"); message.setIntProperty("myCounter", 24); listener.onMessage(message, session); assertDefaultListenerMethodInvocation(); } @Test public void resolveHeaders() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, Map.class); Session session = mock(Session.class); StubTextMessage message = createSimpleJmsTextMessage("my payload"); message.setIntProperty("customInt", 1234); message.setJMSMessageID("abcd-1234"); listener.onMessage(message, session); assertDefaultListenerMethodInvocation(); } @Test public void resolveMessageHeaders() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(MessageHeaders.class); Session session = mock(Session.class); StubTextMessage message = createSimpleJmsTextMessage("my payload"); message.setLongProperty("customLong", 4567L); message.setJMSType("myMessageType"); listener.onMessage(message, session); assertDefaultListenerMethodInvocation(); } @Test public void resolveJmsMessageHeaderAccessor() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(JmsMessageHeaderAccessor.class); Session session = mock(Session.class); StubTextMessage message = createSimpleJmsTextMessage("my payload"); message.setBooleanProperty("customBoolean", true); message.setJMSPriority(9); listener.onMessage(message, session); assertDefaultListenerMethodInvocation(); } @Test public void resolveObjectPayload() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(MyBean.class); MyBean myBean = new MyBean(); myBean.name = "myBean name"; Session session = mock(Session.class); ObjectMessage message = mock(ObjectMessage.class); given(message.getObject()).willReturn(myBean); listener.onMessage(message, session); assertDefaultListenerMethodInvocation(); } @Test public void resolveConvertedPayload() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(Integer.class); Session session = mock(Session.class); listener.onMessage(createSimpleJmsTextMessage("33"), session); assertDefaultListenerMethodInvocation(); } @Test public void processAndReply() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(String.class); String body = "echo text"; String correlationId = "link-1234"; Destination replyDestination = new Destination() {}; TextMessage reply = mock(TextMessage.class); QueueSender queueSender = mock(QueueSender.class); Session session = mock(Session.class); given(session.createTextMessage(body)).willReturn(reply); given(session.createProducer(replyDestination)).willReturn(queueSender); listener.setDefaultResponseDestination(replyDestination); StubTextMessage inputMessage = createSimpleJmsTextMessage(body); inputMessage.setJMSCorrelationID(correlationId); listener.onMessage(inputMessage, session); assertDefaultListenerMethodInvocation(); verify(reply).setJMSCorrelationID(correlationId); verify(queueSender).send(reply); verify(queueSender).close(); } @Test public void processAndReplyWithSendToQueue() throws JMSException { String methodName = "processAndReplyWithSendTo"; SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); MessagingMessageListenerAdapter listener = createInstance(this.factory, getListenerMethod(methodName, String.class), container); processAndReplyWithSendTo(listener, "replyDestination", false); assertListenerMethodInvocation(this.sample, methodName); } @Test public void processFromTopicAndReplyWithSendToQueue() throws JMSException { String methodName = "processAndReplyWithSendTo"; SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setPubSubDomain(true); container.setReplyPubSubDomain(false); MessagingMessageListenerAdapter listener = createInstance(this.factory, getListenerMethod(methodName, String.class), container); processAndReplyWithSendTo(listener, "replyDestination", false); assertListenerMethodInvocation(this.sample, methodName); } @Test public void processAndReplyWithSendToTopic() throws JMSException { String methodName = "processAndReplyWithSendTo"; SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setPubSubDomain(true); MessagingMessageListenerAdapter listener = createInstance(this.factory, getListenerMethod(methodName, String.class), container); processAndReplyWithSendTo(listener, "replyDestination", true); assertListenerMethodInvocation(this.sample, methodName); } @Test public void processFromQueueAndReplyWithSendToTopic() throws JMSException { String methodName = "processAndReplyWithSendTo"; SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setReplyPubSubDomain(true); MessagingMessageListenerAdapter listener = createInstance(this.factory, getListenerMethod(methodName, String.class), container); processAndReplyWithSendTo(listener, "replyDestination", true); assertListenerMethodInvocation(this.sample, methodName); } @Test public void processAndReplyWithDefaultSendTo() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(String.class); processAndReplyWithSendTo(listener, "defaultReply", false); assertDefaultListenerMethodInvocation(); } @Test public void processAndReplyWithCustomReplyQosSettings() throws JMSException { String methodName = "processAndReplyWithSendTo"; SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); QosSettings replyQosSettings = new QosSettings(1, 6, 6000); container.setReplyQosSettings(replyQosSettings); MessagingMessageListenerAdapter listener = createInstance(this.factory, getListenerMethod(methodName, String.class), container); processAndReplyWithSendTo(listener, "replyDestination", false, replyQosSettings); assertListenerMethodInvocation(this.sample, methodName); } @Test public void processAndReplyWithNullReplyQosSettings() throws JMSException { String methodName = "processAndReplyWithSendTo"; SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setReplyQosSettings(null); MessagingMessageListenerAdapter listener = createInstance(this.factory, getListenerMethod(methodName, String.class), container); processAndReplyWithSendTo(listener, "replyDestination", false); assertListenerMethodInvocation(this.sample, methodName); } private void processAndReplyWithSendTo(MessagingMessageListenerAdapter listener, String replyDestinationName, boolean pubSubDomain) throws JMSException { processAndReplyWithSendTo(listener, replyDestinationName, pubSubDomain, null); } private void processAndReplyWithSendTo(MessagingMessageListenerAdapter listener, String replyDestinationName, boolean pubSubDomain, QosSettings replyQosSettings) throws JMSException { String body = "echo text"; String correlationId = "link-1234"; Destination replyDestination = new Destination() {}; DestinationResolver destinationResolver = mock(DestinationResolver.class); TextMessage reply = mock(TextMessage.class); QueueSender queueSender = mock(QueueSender.class); Session session = mock(Session.class); given(destinationResolver.resolveDestinationName(session, replyDestinationName, pubSubDomain)) .willReturn(replyDestination); given(session.createTextMessage(body)).willReturn(reply); given(session.createProducer(replyDestination)).willReturn(queueSender); listener.setDestinationResolver(destinationResolver); StubTextMessage inputMessage = createSimpleJmsTextMessage(body); inputMessage.setJMSCorrelationID(correlationId); listener.onMessage(inputMessage, session); verify(destinationResolver).resolveDestinationName(session, replyDestinationName, pubSubDomain); verify(reply).setJMSCorrelationID(correlationId); if (replyQosSettings != null) { verify(queueSender).send(reply, replyQosSettings.getDeliveryMode(), replyQosSettings.getPriority(), replyQosSettings.getTimeToLive()); } else { verify(queueSender).send(reply); } verify(queueSender).close(); } @Test public void emptySendTo() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(String.class); TextMessage reply = mock(TextMessage.class); Session session = mock(Session.class); given(session.createTextMessage("content")).willReturn(reply); this.thrown.expect(ReplyFailureException.class); this.thrown.expectCause(Matchers.isA(InvalidDestinationException.class)); listener.onMessage(createSimpleJmsTextMessage("content"), session); } @Test public void invalidSendTo() { this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage("firstDestination"); this.thrown.expectMessage("secondDestination"); createDefaultInstance(String.class); } @Test public void validatePayloadValid() throws JMSException { String methodName = "validatePayload"; DefaultMessageHandlerMethodFactory customFactory = new DefaultMessageHandlerMethodFactory(); customFactory.setValidator(testValidator("invalid value")); initializeFactory(customFactory); Method method = getListenerMethod(methodName, String.class); MessagingMessageListenerAdapter listener = createInstance(customFactory, method); Session session = mock(Session.class); listener.onMessage(createSimpleJmsTextMessage("test"), session); // test is a valid value assertListenerMethodInvocation(this.sample, methodName); } @Test public void validatePayloadInvalid() throws JMSException { DefaultMessageHandlerMethodFactory customFactory = new DefaultMessageHandlerMethodFactory(); customFactory.setValidator(testValidator("invalid value")); Method method = getListenerMethod("validatePayload", String.class); MessagingMessageListenerAdapter listener = createInstance(customFactory, method); Session session = mock(Session.class); this.thrown.expect(ListenerExecutionFailedException.class); listener.onMessage(createSimpleJmsTextMessage("invalid value"), session); // test is an invalid value } // failure scenario @Test public void invalidPayloadType() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(Integer.class); Session session = mock(Session.class); this.thrown.expect(ListenerExecutionFailedException.class); this.thrown.expectCause(Matchers.isA(MessageConversionException.class)); this.thrown.expectMessage(getDefaultListenerMethod(Integer.class).toGenericString()); // ref to method listener.onMessage(createSimpleJmsTextMessage("test"), session); // test is not a valid integer } @Test public void invalidMessagePayloadType() throws JMSException { MessagingMessageListenerAdapter listener = createDefaultInstance(Message.class); Session session = mock(Session.class); this.thrown.expect(ListenerExecutionFailedException.class); this.thrown.expectCause(Matchers.isA(MessageConversionException.class)); listener.onMessage(createSimpleJmsTextMessage("test"), session); // Message<String> as Message<Integer> } private MessagingMessageListenerAdapter createInstance( DefaultMessageHandlerMethodFactory factory, Method method, MessageListenerContainer container) { MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint(); endpoint.setBean(this.sample); endpoint.setMethod(method); endpoint.setMessageHandlerMethodFactory(factory); return endpoint.createMessageListener(container); } private MessagingMessageListenerAdapter createInstance(DefaultMessageHandlerMethodFactory factory, Method method) { return createInstance(factory, method, new SimpleMessageListenerContainer()); } private MessagingMessageListenerAdapter createDefaultInstance(Class<?>... parameterTypes) { return createInstance(this.factory, getDefaultListenerMethod(parameterTypes)); } private StubTextMessage createSimpleJmsTextMessage(String body) { return new StubTextMessage(body); } private Method getListenerMethod(String methodName, Class<?>... parameterTypes) { Method method = ReflectionUtils.findMethod(JmsEndpointSampleBean.class, methodName, parameterTypes); assertNotNull("no method found with name " + methodName + " and parameters " + Arrays.toString(parameterTypes)); return method; } private Method getDefaultListenerMethod(Class<?>... parameterTypes) { return getListenerMethod(this.name.getMethodName(), parameterTypes); } private void assertDefaultListenerMethodInvocation() { assertListenerMethodInvocation(this.sample, this.name.getMethodName()); } private void assertListenerMethodInvocation(JmsEndpointSampleBean bean, String methodName) { assertTrue("Method " + methodName + " should have been invoked", bean.invocations.get(methodName)); } private void initializeFactory(DefaultMessageHandlerMethodFactory factory) { factory.setBeanFactory(new StaticListableBeanFactory()); factory.afterPropertiesSet(); } private Validator testValidator(final String invalidValue) { return new Validator() { @Override public boolean supports(Class<?> clazz) { return String.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { String value = (String) target; if (invalidValue.equals(value)) { errors.reject("not a valid value"); } } }; } private Method getTestMethod() { return ReflectionUtils.findMethod(MethodJmsListenerEndpointTests.class, this.name.getMethodName()); } @SendTo("defaultReply") @SuppressWarnings("unused") static class JmsEndpointSampleBean { private final Map<String, Boolean> invocations = new HashMap<>(); public void resolveMessageAndSession(javax.jms.Message message, Session session) { this.invocations.put("resolveMessageAndSession", true); assertNotNull("Message not injected", message); assertNotNull("Session not injected", session); } public void resolveGenericMessage(Message<String> message) { this.invocations.put("resolveGenericMessage", true); assertNotNull("Generic message not injected", message); assertEquals("Wrong message payload", "test", message.getPayload()); } public void resolveHeaderAndPayload(@Payload String content, @Header int myCounter) { this.invocations.put("resolveHeaderAndPayload", true); assertEquals("Wrong @Payload resolution", "my payload", content); assertEquals("Wrong @Header resolution", 55, myCounter); } public void resolveCustomHeaderNameAndPayload(@Payload String content, @Header("myCounter") int counter) { this.invocations.put("resolveCustomHeaderNameAndPayload", true); assertEquals("Wrong @Payload resolution", "my payload", content); assertEquals("Wrong @Header resolution", 24, counter); } public void resolveCustomHeaderNameAndPayloadWithHeaderNameSet(@Payload String content, @Header(name = "myCounter") int counter) { this.invocations.put("resolveCustomHeaderNameAndPayloadWithHeaderNameSet", true); assertEquals("Wrong @Payload resolution", "my payload", content); assertEquals("Wrong @Header resolution", 24, counter); } public void resolveHeaders(String content, @Headers Map<String, Object> headers) { this.invocations.put("resolveHeaders", true); assertEquals("Wrong payload resolution", "my payload", content); assertNotNull("headers not injected", headers); assertEquals("Missing JMS message id header", "abcd-1234", headers.get(JmsHeaders.MESSAGE_ID)); assertEquals("Missing custom header", 1234, headers.get("customInt")); } public void resolveMessageHeaders(MessageHeaders headers) { this.invocations.put("resolveMessageHeaders", true); assertNotNull("MessageHeaders not injected", headers); assertEquals("Missing JMS message type header", "myMessageType", headers.get(JmsHeaders.TYPE)); assertEquals("Missing custom header", 4567L, (long) headers.get("customLong"), 0.0); } public void resolveJmsMessageHeaderAccessor(JmsMessageHeaderAccessor headers) { this.invocations.put("resolveJmsMessageHeaderAccessor", true); assertNotNull("MessageHeaders not injected", headers); assertEquals("Missing JMS message priority header", Integer.valueOf(9), headers.getPriority()); assertEquals("Missing custom header", true, headers.getHeader("customBoolean")); } public void resolveObjectPayload(MyBean bean) { this.invocations.put("resolveObjectPayload", true); assertNotNull("Object payload not injected", bean); assertEquals("Wrong content for payload", "myBean name", bean.name); } public void resolveConvertedPayload(Integer counter) { this.invocations.put("resolveConvertedPayload", true); assertNotNull("Payload not injected", counter); assertEquals("Wrong content for payload", Integer.valueOf(33), counter); } public String processAndReply(@Payload String content) { this.invocations.put("processAndReply", true); return content; } @SendTo("replyDestination") public String processAndReplyWithSendTo(String content) { this.invocations.put("processAndReplyWithSendTo", true); return content; } public String processAndReplyWithDefaultSendTo(String content) { this.invocations.put("processAndReplyWithDefaultSendTo", true); return content; } @SendTo("") public String emptySendTo(String content) { this.invocations.put("emptySendTo", true); return content; } @SendTo({"firstDestination", "secondDestination"}) public String invalidSendTo(String content) { this.invocations.put("invalidSendTo", true); return content; } public void validatePayload(@Validated String payload) { this.invocations.put("validatePayload", true); } public void invalidPayloadType(@Payload Integer payload) { throw new IllegalStateException("Should never be called."); } public void invalidMessagePayloadType(Message<Integer> message) { throw new IllegalStateException("Should never be called."); } } @SuppressWarnings("serial") static class MyBean implements Serializable { private String name; } }