/*
* 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.listener.adapter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import com.fasterxml.jackson.annotation.JsonView;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.jms.StubTextMessage;
import org.springframework.jms.support.JmsHeaders;
import org.springframework.jms.support.QosSettings;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.ReflectionUtils;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* @author Stephane Nicoll
*/
public class MessagingMessageListenerAdapterTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private static final Destination sharedReplyDestination = mock(Destination.class);
private final DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
private final SampleBean sample = new SampleBean();
@Before
public void setup() {
initializeFactory(factory);
}
@Test
public void buildMessageWithStandardMessage() throws JMSException {
Destination replyTo = new Destination() {};
Message<String> result = MessageBuilder.withPayload("Response")
.setHeader("foo", "bar")
.setHeader(JmsHeaders.TYPE, "msg_type")
.setHeader(JmsHeaders.REPLY_TO, replyTo)
.build();
Session session = mock(Session.class);
given(session.createTextMessage("Response")).willReturn(new StubTextMessage("Response"));
MessagingMessageListenerAdapter listener = getSimpleInstance("echo", Message.class);
javax.jms.Message replyMessage = listener.buildMessage(session, result);
verify(session).createTextMessage("Response");
assertNotNull("reply should never be null", replyMessage);
assertEquals("Response", ((TextMessage) replyMessage).getText());
assertEquals("custom header not copied", "bar", replyMessage.getStringProperty("foo"));
assertEquals("type header not copied", "msg_type", replyMessage.getJMSType());
assertEquals("replyTo header not copied", replyTo, replyMessage.getJMSReplyTo());
}
@Test
public void exceptionInListener() {
javax.jms.Message message = new StubTextMessage("foo");
Session session = mock(Session.class);
MessagingMessageListenerAdapter listener = getSimpleInstance("fail", String.class);
try {
listener.onMessage(message, session);
fail("Should have thrown an exception");
}
catch (JMSException ex) {
fail("Should not have thrown a JMS exception");
}
catch (ListenerExecutionFailedException ex) {
assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
assertEquals("Expected test exception", ex.getCause().getMessage());
}
}
@Test
public void exceptionInInvocation() {
javax.jms.Message message = new StubTextMessage("foo");
Session session = mock(Session.class);
MessagingMessageListenerAdapter listener = getSimpleInstance("wrongParam", Integer.class);
try {
listener.onMessage(message, session);
fail("Should have thrown an exception");
}
catch (JMSException ex) {
fail("Should not have thrown a JMS exception");
}
catch (ListenerExecutionFailedException ex) {
assertEquals(MessageConversionException.class, ex.getCause().getClass());
}
}
@Test
public void payloadConversionLazilyInvoked() throws JMSException {
javax.jms.Message jmsMessage = mock(javax.jms.Message.class);
MessageConverter messageConverter = mock(MessageConverter.class);
given(messageConverter.fromMessage(jmsMessage)).willReturn("FooBar");
MessagingMessageListenerAdapter listener = getSimpleInstance("simple", Message.class);
listener.setMessageConverter(messageConverter);
Message<?> message = listener.toMessagingMessage(jmsMessage);
verify(messageConverter, never()).fromMessage(jmsMessage);
assertEquals("FooBar", message.getPayload());
verify(messageConverter, times(1)).fromMessage(jmsMessage);
}
@Test
public void headerConversionLazilyInvoked() throws JMSException {
javax.jms.Message jmsMessage = mock(javax.jms.Message.class);
when(jmsMessage.getPropertyNames()).thenThrow(new IllegalArgumentException("Header failure"));
MessagingMessageListenerAdapter listener = getSimpleInstance("simple", Message.class);
Message<?> message = listener.toMessagingMessage(jmsMessage);
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Header failure");
message.getHeaders(); // Triggers headers resolution
}
@Test
public void incomingMessageUsesMessageConverter() throws JMSException {
javax.jms.Message jmsMessage = mock(javax.jms.Message.class);
Session session = mock(Session.class);
MessageConverter messageConverter = mock(MessageConverter.class);
given(messageConverter.fromMessage(jmsMessage)).willReturn("FooBar");
MessagingMessageListenerAdapter listener = getSimpleInstance("simple", Message.class);
listener.setMessageConverter(messageConverter);
listener.onMessage(jmsMessage, session);
verify(messageConverter, times(1)).fromMessage(jmsMessage);
assertEquals(1, sample.simples.size());
assertEquals("FooBar", sample.simples.get(0).getPayload());
}
@Test
public void replyUsesMessageConverterForPayload() throws JMSException {
Session session = mock(Session.class);
MessageConverter messageConverter = mock(MessageConverter.class);
given(messageConverter.toMessage("Response", session)).willReturn(new StubTextMessage("Response"));
Message<String> result = MessageBuilder.withPayload("Response").build();
MessagingMessageListenerAdapter listener = getSimpleInstance("echo", Message.class);
listener.setMessageConverter(messageConverter);
javax.jms.Message replyMessage = listener.buildMessage(session, result);
verify(messageConverter, times(1)).toMessage("Response", session);
assertNotNull("reply should never be null", replyMessage);
assertEquals("Response", ((TextMessage) replyMessage).getText());
}
@Test
public void replyPayloadToQueue() throws JMSException {
Session session = mock(Session.class);
Queue replyDestination = mock(Queue.class);
given(session.createQueue("queueOut")).willReturn(replyDestination);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToQueue", Message.class);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session).createQueue("queueOut");
verify(session).createTextMessage("Response");
verify(messageProducer).send(responseMessage);
verify(messageProducer).close();
}
@Test
public void replyWithCustomTimeToLive() throws JMSException {
Session session = mock(Session.class);
Queue replyDestination = mock(Queue.class);
given(session.createQueue("queueOut")).willReturn(replyDestination);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToQueue", Message.class);
QosSettings settings = new QosSettings();
settings.setTimeToLive(6000);
listener.setResponseQosSettings(settings);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session).createQueue("queueOut");
verify(session).createTextMessage("Response");
verify(messageProducer).send(responseMessage, javax.jms.Message.DEFAULT_DELIVERY_MODE,
javax.jms.Message.DEFAULT_PRIORITY, 6000);
verify(messageProducer).close();
}
@Test
public void replyWithFullQoS() throws JMSException {
Session session = mock(Session.class);
Queue replyDestination = mock(Queue.class);
given(session.createQueue("queueOut")).willReturn(replyDestination);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToQueue", Message.class);
QosSettings settings = new QosSettings(DeliveryMode.NON_PERSISTENT, 6, 6000);
listener.setResponseQosSettings(settings);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session).createQueue("queueOut");
verify(session).createTextMessage("Response");
verify(messageProducer).send(responseMessage, DeliveryMode.NON_PERSISTENT, 6, 6000);
verify(messageProducer).close();
}
@Test
public void replyPayloadToTopic() throws JMSException {
Session session = mock(Session.class);
Topic replyDestination = mock(Topic.class);
given(session.createTopic("topicOut")).willReturn(replyDestination);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToTopic", Message.class);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session).createTopic("topicOut");
verify(session).createTextMessage("Response");
verify(messageProducer).send(responseMessage);
verify(messageProducer).close();
}
@Test
public void replyPayloadToDestination() throws JMSException {
Session session = mock(Session.class);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(sharedReplyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener = getPayloadInstance("Response", "replyPayloadToDestination", Message.class);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session, times(0)).createQueue(anyString());
verify(session).createTextMessage("Response");
verify(messageProducer).send(responseMessage);
verify(messageProducer).close();
}
@Test
public void replyPayloadNoDestination() throws JMSException {
Queue replyDestination = mock(Queue.class);
Session session = mock(Session.class);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage("Response")).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener =
getPayloadInstance("Response", "replyPayloadNoDestination", Message.class);
listener.setDefaultResponseDestination(replyDestination);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session, times(0)).createQueue(anyString());
verify(session).createTextMessage("Response");
verify(messageProducer).send(responseMessage);
verify(messageProducer).close();
}
@Test
public void replyJackson() throws JMSException {
TextMessage reply = testReplyWithJackson("replyJackson",
"{\"counter\":42,\"name\":\"Response\",\"description\":\"lengthy description\"}");
verify(reply).setObjectProperty("foo", "bar");
}
@Test
public void replyJacksonMessageAndJsonView() throws JMSException {
TextMessage reply = testReplyWithJackson("replyJacksonMessageAndJsonView",
"{\"name\":\"Response\"}");
verify(reply).setObjectProperty("foo", "bar");
}
@Test
public void replyJacksonPojoAndJsonView() throws JMSException {
TextMessage reply = testReplyWithJackson("replyJacksonPojoAndJsonView",
"{\"name\":\"Response\"}");
verify(reply, never()).setObjectProperty("foo", "bar");
}
public TextMessage testReplyWithJackson(String methodName, String replyContent) throws JMSException {
Queue replyDestination = mock(Queue.class);
Session session = mock(Session.class);
MessageProducer messageProducer = mock(MessageProducer.class);
TextMessage responseMessage = mock(TextMessage.class);
given(session.createTextMessage(replyContent)).willReturn(responseMessage);
given(session.createProducer(replyDestination)).willReturn(messageProducer);
MessagingMessageListenerAdapter listener = getPayloadInstance("Response", methodName, Message.class);
MappingJackson2MessageConverter messageConverter = new MappingJackson2MessageConverter();
messageConverter.setTargetType(MessageType.TEXT);
listener.setMessageConverter(messageConverter);
listener.setDefaultResponseDestination(replyDestination);
listener.onMessage(mock(javax.jms.Message.class), session);
verify(session, times(0)).createQueue(anyString());
verify(session).createTextMessage(replyContent);
verify(messageProducer).send(responseMessage);
verify(messageProducer).close();
return responseMessage;
}
protected MessagingMessageListenerAdapter getSimpleInstance(String methodName, Class... parameterTypes) {
Method m = ReflectionUtils.findMethod(SampleBean.class, methodName, parameterTypes);
return createInstance(m);
}
protected MessagingMessageListenerAdapter createInstance(Method m) {
MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter();
adapter.setHandlerMethod(factory.createInvocableHandlerMethod(sample, m));
return adapter;
}
protected MessagingMessageListenerAdapter getPayloadInstance(final Object payload,
String methodName, Class... parameterTypes) {
Method method = ReflectionUtils.findMethod(SampleBean.class, methodName, parameterTypes);
MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter() {
@Override
protected Object extractMessage(javax.jms.Message message) {
return payload;
}
};
adapter.setHandlerMethod(factory.createInvocableHandlerMethod(sample, method));
return adapter;
}
private void initializeFactory(DefaultMessageHandlerMethodFactory factory) {
factory.setBeanFactory(new StaticListableBeanFactory());
factory.afterPropertiesSet();
}
@SuppressWarnings("unused")
private static class SampleBean {
public final List<Message<String>> simples = new ArrayList<>();
public void simple(Message<String> input) {
simples.add(input);
}
public Message<String> echo(Message<String> input) {
return MessageBuilder.withPayload(input.getPayload())
.setHeader(JmsHeaders.TYPE, "reply")
.build();
}
public JmsResponse<String> replyPayloadToQueue(Message<String> input) {
return JmsResponse.forQueue(input.getPayload(), "queueOut");
}
public JmsResponse<String> replyPayloadToTopic(Message<String> input) {
return JmsResponse.forTopic(input.getPayload(), "topicOut");
}
public JmsResponse<String> replyPayloadToDestination(Message<String> input) {
return JmsResponse.forDestination(input.getPayload(), sharedReplyDestination);
}
public JmsResponse<String> replyPayloadNoDestination(Message<String> input) {
return new JmsResponse<>(input.getPayload(), null);
}
public Message<SampleResponse> replyJackson(Message<String> input) {
return MessageBuilder.withPayload(createSampleResponse(input.getPayload()))
.setHeader("foo", "bar").build();
}
@JsonView(Summary.class)
public Message<SampleResponse> replyJacksonMessageAndJsonView(Message<String> input) {
return MessageBuilder.withPayload(createSampleResponse(input.getPayload()))
.setHeader("foo", "bar").build();
}
@JsonView(Summary.class)
public SampleResponse replyJacksonPojoAndJsonView(Message<String> input) {
return createSampleResponse(input.getPayload());
}
private SampleResponse createSampleResponse(String name) {
return new SampleResponse(name, "lengthy description");
}
public void fail(String input) {
throw new IllegalArgumentException("Expected test exception");
}
public void wrongParam(Integer i) {
throw new IllegalArgumentException("Should not have been called");
}
}
interface Summary {};
interface Full extends Summary {};
private static class SampleResponse {
private int counter = 42;
@JsonView(Summary.class)
private String name;
@JsonView(Full.class)
private String description;
SampleResponse() {
}
public SampleResponse(String name, String description) {
this.name = name;
this.description = description;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
}