From 9fabcad3dd40296620e0c2832f9f045076600966 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 26 May 2014 09:32:54 +0200 Subject: [PATCH] Revisit JmsMessagingTemplate This commit revisits JmsMessagingTemplate and adds support for receiving operations as well. JmsMessageSendingOperations has been renamed to JmsMessageOperations. The messaging abstraction did not split receiving and request-reply operations. AbstractMessageReceivingTemplate has been created to hold only the receiving operations. Issue: SPR-11772 --- ...rations.java => JmsMessageOperations.java} | 26 ++- .../jms/messaging/JmsMessagingTemplate.java | 71 +++++++- .../converter/MessagingMessageConverter.java | 3 + .../messaging/JmsMessagingTemplateTests.java | 152 ++++++++++++++++++ .../MessagingMessageConverterTests.java | 5 + .../AbstractMessageReceivingTemplate.java | 63 ++++++++ .../core/AbstractMessagingTemplate.java | 40 +---- 7 files changed, 317 insertions(+), 43 deletions(-) rename spring-jms/src/main/java/org/springframework/jms/messaging/{JmsMessageSendingOperations.java => JmsMessageOperations.java} (74%) create mode 100644 spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageReceivingTemplate.java diff --git a/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageSendingOperations.java b/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageOperations.java similarity index 74% rename from spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageSendingOperations.java rename to spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageOperations.java index 286db80e85..9b48ec2759 100644 --- a/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageSendingOperations.java +++ b/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageOperations.java @@ -23,18 +23,20 @@ import javax.jms.Destination; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.messaging.core.MessagePostProcessor; +import org.springframework.messaging.core.MessageReceivingOperations; import org.springframework.messaging.core.MessageSendingOperations; /** - * A specialization of {@link MessageSendingOperations} for JMS related - * operations that allows to specify a destination name rather than the + * A specialization of {@link MessageSendingOperations} and {@link MessageSendingOperations} + * for JMS related operations that allows to specify a destination name rather than the * actual {@link javax.jms.Destination} * * @author Stephane Nicoll * @since 4.1 * @see org.springframework.jms.core.JmsTemplate */ -public interface JmsMessageSendingOperations extends MessageSendingOperations { +public interface JmsMessageOperations + extends MessageSendingOperations, MessageReceivingOperations { /** * Send a message to the given destination. @@ -89,4 +91,22 @@ public interface JmsMessageSendingOperations extends MessageSendingOperations headers, MessagePostProcessor postProcessor) throws MessagingException; + /** + * Receive a message from the given destination. + * @param destinationName the name of the target destination + * @return the received message, possibly {@code null} if the message could not + * be received, for example due to a timeout + */ + Message receive(String destinationName) throws MessagingException; + + /** + * Receive a message from the given destination and convert its payload to the + * specified target class. + * @param destinationName the name of the target destination + * @param targetClass the target class to convert the payload to + * @return the converted payload of the reply message, possibly {@code null} if + * the message could not be received, for example due to a timeout + */ + T receiveAndConvert(String destinationName, Class targetClass) throws MessagingException; + } diff --git a/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessagingTemplate.java b/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessagingTemplate.java index 25bac284da..4e139446c6 100644 --- a/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessagingTemplate.java +++ b/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessagingTemplate.java @@ -25,25 +25,26 @@ import javax.jms.Session; import org.springframework.beans.factory.InitializingBean; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; +import org.springframework.jms.support.converter.MessageConversionException; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.converter.MessagingMessageConverter; import org.springframework.jms.support.converter.SimpleJmsHeaderMapper; import org.springframework.jms.support.converter.SimpleMessageConverter; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; -import org.springframework.messaging.core.AbstractMessageSendingTemplate; +import org.springframework.messaging.core.AbstractMessageReceivingTemplate; import org.springframework.messaging.core.MessagePostProcessor; import org.springframework.util.Assert; /** - * An implementation of {@link JmsMessageSendingOperations}. + * An implementation of {@link JmsMessageOperations}. * * @author Stephane Nicoll * @since 4.1 */ public class JmsMessagingTemplate - extends AbstractMessageSendingTemplate - implements JmsMessageSendingOperations, InitializingBean { + extends AbstractMessageReceivingTemplate + implements JmsMessageOperations, InitializingBean { private JmsTemplate jmsTemplate; @@ -165,6 +166,45 @@ public class JmsMessagingTemplate send(destinationName, message); } + @Override + public Message receive() { + Destination defaultDestination = getDefaultDestination(); + if (defaultDestination != null) { + return receive(defaultDestination); + } + else { + return receive(getRequiredDefaultDestinationName()); + } + } + + @Override + public T receiveAndConvert(Class targetClass) { + Destination defaultDestination = getDefaultDestination(); + if (defaultDestination != null) { + return receiveAndConvert(defaultDestination, targetClass); + } + else { + return receiveAndConvert(getRequiredDefaultDestinationName(), targetClass); + } + } + + @Override + public Message receive(String destinationName) throws MessagingException { + return doReceive(destinationName); + } + + @Override + @SuppressWarnings("unchecked") + public T receiveAndConvert(String destinationName, Class targetClass) throws MessagingException { + Message message = doReceive(destinationName); + if (message != null) { + return (T) getMessageConverter().fromMessage(message, targetClass); + } + else { + return null; + } + } + @Override protected void doSend(Destination destination, Message message) { jmsTemplate.send(destination, new MessagingMessageCreator(message, this.jmsMessageConverter)); @@ -174,6 +214,17 @@ public class JmsMessagingTemplate jmsTemplate.send(destinationName, new MessagingMessageCreator(message, this.jmsMessageConverter)); } + @Override + protected Message doReceive(Destination destination) { + javax.jms.Message jmsMessage = jmsTemplate.receive(destination); + return doConvert(jmsMessage); + } + + protected Message doReceive(String destinationName) { + javax.jms.Message jmsMessage = jmsTemplate.receive(destinationName); + return doConvert(jmsMessage); + } + protected String getRequiredDefaultDestinationName() { String name = getDefaultDestinationName(); if (name == null) { @@ -185,6 +236,18 @@ public class JmsMessagingTemplate return name; } + protected Message doConvert(javax.jms.Message message) { + if (message == null) { + return null; + } + try { + return (Message) jmsMessageConverter.fromMessage(message); + } + catch (JMSException e) { + throw new MessageConversionException("Could not convert '" + message + "'", e); + } + } + private static class MessagingMessageCreator implements MessageCreator { diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java index e3206284dc..b41d0ffa9c 100644 --- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java +++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java @@ -98,6 +98,9 @@ public class MessagingMessageConverter implements MessageConverter, Initializing @SuppressWarnings("unchecked") @Override public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException { + if (message == null) { + return null; + } Map mappedHeaders = this.headerMapper.toHeaders(message); Object convertedObject = extractPayload(message); MessageBuilder builder = (convertedObject instanceof org.springframework.messaging.Message) ? diff --git a/spring-jms/src/test/java/org/springframework/jms/messaging/JmsMessagingTemplateTests.java b/spring-jms/src/test/java/org/springframework/jms/messaging/JmsMessagingTemplateTests.java index 30e145ec22..ffdfe97c31 100644 --- a/spring-jms/src/test/java/org/springframework/jms/messaging/JmsMessagingTemplateTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/messaging/JmsMessagingTemplateTests.java @@ -17,9 +17,13 @@ package org.springframework.jms.messaging; import static org.junit.Assert.*; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.*; +import static org.mockito.BDDMockito.verify; import static org.mockito.Mockito.mock; +import java.io.Writer; import java.util.HashMap; import java.util.Map; @@ -29,6 +33,7 @@ import javax.jms.Session; import javax.jms.TextMessage; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -39,12 +44,14 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.jms.StubTextMessage; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; import org.springframework.jms.support.converter.MessageConversionException; import org.springframework.jms.support.converter.SimpleMessageConverter; import org.springframework.messaging.Message; +import org.springframework.messaging.converter.GenericMessageConverter; import org.springframework.messaging.support.MessageBuilder; /** @@ -216,12 +223,151 @@ public class JmsMessagingTemplateTests { assertTextMessage(messageCreator.getValue()); // see createTextMessage } + @Test + public void receive() { + Destination destination = new Destination() {}; + javax.jms.Message jmsMessage = createJmsTextMessage(); + given(jmsTemplate.receive(destination)).willReturn(jmsMessage); + + Message message = messagingTemplate.receive(destination); + verify(jmsTemplate).receive(destination); + assertTextMessage(message); + } + + @Test + public void receiveName() { + javax.jms.Message jmsMessage = createJmsTextMessage(); + given(jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + Message message = messagingTemplate.receive("myQueue"); + verify(jmsTemplate).receive("myQueue"); + assertTextMessage(message); + } + + @Test + public void receiveDefaultDestination() { + Destination destination = new Destination() {}; + messagingTemplate.setDefaultDestination(destination); + javax.jms.Message jmsMessage = createJmsTextMessage(); + given(jmsTemplate.receive(destination)).willReturn(jmsMessage); + + Message message = messagingTemplate.receive(); + verify(jmsTemplate).receive(destination); + assertTextMessage(message); + } + + @Test + public void receiveDefaultDestinationName() { + messagingTemplate.setDefaultDestinationName("myQueue"); + javax.jms.Message jmsMessage = createJmsTextMessage(); + given(jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + Message message = messagingTemplate.receive(); + verify(jmsTemplate).receive("myQueue"); + assertTextMessage(message); + } + + @Test + public void receiveNoDefaultSet() { + thrown.expect(IllegalStateException.class); + messagingTemplate.receive(); + } + + @Test + public void receiveAndConvert() { + Destination destination = new Destination() {}; + javax.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(jmsTemplate.receive(destination)).willReturn(jmsMessage); + + String payload = messagingTemplate.receiveAndConvert(destination, String.class); + assertEquals("my Payload", payload); + verify(jmsTemplate).receive(destination); + } + + @Test + public void receiveAndConvertName() { + javax.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + String payload = messagingTemplate.receiveAndConvert("myQueue", String.class); + assertEquals("my Payload", payload); + verify(jmsTemplate).receive("myQueue"); + } + + @Test + public void receiveAndConvertDefaultDestination() { + Destination destination = new Destination() {}; + messagingTemplate.setDefaultDestination(destination); + javax.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(jmsTemplate.receive(destination)).willReturn(jmsMessage); + + String payload = messagingTemplate.receiveAndConvert(String.class); + assertEquals("my Payload", payload); + verify(jmsTemplate).receive(destination); + } + + @Test + public void receiveAndConvertDefaultDestinationName() { + messagingTemplate.setDefaultDestinationName("myQueue"); + javax.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + String payload = messagingTemplate.receiveAndConvert(String.class); + assertEquals("my Payload", payload); + verify(jmsTemplate).receive("myQueue"); + } + + @Test + public void receiveAndConvertWithConversion() { + javax.jms.Message jmsMessage = createJmsTextMessage("123"); + given(jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + messagingTemplate.setMessageConverter(new GenericMessageConverter(new DefaultConversionService())); + + Integer payload = messagingTemplate.receiveAndConvert("myQueue", Integer.class); + assertEquals(Integer.valueOf(123), payload); + verify(jmsTemplate).receive("myQueue"); + } + + @Test + @Ignore("SPR-11817") + public void receiveAndConvertNoConverter() { + javax.jms.Message jmsMessage = createJmsTextMessage("Hello"); + given(jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + thrown.expect(MessageConversionException.class); + messagingTemplate.receiveAndConvert("myQueue", Writer.class); + } + + @Test + public void receiveAndConvertNoInput() { + given(jmsTemplate.receive("myQueue")).willReturn(null); + + assertNull(messagingTemplate.receiveAndConvert("myQueue", String.class)); + } + private Message createTextMessage() { return MessageBuilder .withPayload("Hello").setHeader("foo", "bar").build(); } + private javax.jms.Message createJmsTextMessage(String payload) { + try { + StubTextMessage jmsMessage = new StubTextMessage(payload); + jmsMessage.setStringProperty("foo", "bar"); + return jmsMessage; + } + catch (JMSException e) { + throw new IllegalStateException("Should not happen", e); + } + } + + private javax.jms.Message createJmsTextMessage() { + return createJmsTextMessage("Hello"); + } + + private void assertTextMessage(MessageCreator messageCreator) { try { TextMessage jmsMessage = createTextMessage(messageCreator); @@ -233,6 +379,12 @@ public class JmsMessagingTemplateTests { } } + private void assertTextMessage(Message message) { + assertNotNull("message should not be null", message); + assertEquals("Wrong payload", "Hello", message.getPayload()); + assertEquals("Invalid foo property", "bar", message.getHeaders().get("foo")); + } + protected TextMessage createTextMessage(MessageCreator creator) throws JMSException { Session mock = mock(Session.class); diff --git a/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java index 02716006ff..f2272dbd96 100644 --- a/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java @@ -64,6 +64,11 @@ public class MessagingMessageConverterTests { verify(session).createObjectMessage(payload); } + @Test + public void fromNull() throws JMSException { + assertNull(converter.fromMessage(null)); + } + @Test public void customPayloadConverter() throws JMSException { TextMessage jmsMsg = new StubTextMessage("1224"); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageReceivingTemplate.java b/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageReceivingTemplate.java new file mode 100644 index 0000000000..55c4bc9849 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageReceivingTemplate.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2014 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.messaging.core; + +import org.springframework.messaging.Message; + +/** + * An extension of {@link AbstractMessageSendingTemplate} that adds support for + * receive style operations as defined by {@link MessageReceivingOperations}. + * + * @author Mark Fisher + * @author Rossen Stoyanchev + * @author Stephane Nicoll + * @since 4.1 + */ +public abstract class AbstractMessageReceivingTemplate extends AbstractMessageSendingTemplate + implements MessageReceivingOperations { + + @Override + public Message receive() { + return receive(getRequiredDefaultDestination()); + } + + @Override + public Message receive(D destination) { + return doReceive(destination); + } + + protected abstract Message doReceive(D destination); + + + @Override + public T receiveAndConvert(Class targetClass) { + return receiveAndConvert(getRequiredDefaultDestination(), targetClass); + } + + @SuppressWarnings("unchecked") + @Override + public T receiveAndConvert(D destination, Class targetClass) { + Message message = doReceive(destination); + if (message != null) { + return (T) getMessageConverter().fromMessage(message, targetClass); + } + else { + return null; + } + } + +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessagingTemplate.java b/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessagingTemplate.java index 286e12062d..d0817a1788 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessagingTemplate.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessagingTemplate.java @@ -23,47 +23,15 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MessageConversionException; /** - * An extension of {@link AbstractMessageSendingTemplate} that adds support for - * receive as well as request-reply style operations as defined by - * {@link MessageReceivingOperations} and {@link MessageRequestReplyOperations}. + * An extension of {@link AbstractMessageReceivingTemplate} that adds support for + * request-reply style operations as defined by {@link MessageRequestReplyOperations}. * * @author Mark Fisher * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractMessagingTemplate extends AbstractMessageSendingTemplate - implements MessageRequestReplyOperations, MessageReceivingOperations { - - - @Override - public Message receive() { - return receive(getRequiredDefaultDestination()); - } - - @Override - public Message receive(D destination) { - return doReceive(destination); - } - - protected abstract Message doReceive(D destination); - - - @Override - public T receiveAndConvert(Class targetClass) { - return receiveAndConvert(getRequiredDefaultDestination(), targetClass); - } - - @SuppressWarnings("unchecked") - @Override - public T receiveAndConvert(D destination, Class targetClass) { - Message message = doReceive(destination); - if (message != null) { - return (T) getMessageConverter().fromMessage(message, targetClass); - } - else { - return null; - } - } +public abstract class AbstractMessagingTemplate extends AbstractMessageReceivingTemplate + implements MessageRequestReplyOperations { @Override public Message sendAndReceive(Message requestMessage) {