From 7469159bf1ddbb0eac292388883fa1904f98d4f0 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 19 May 2014 10:42:18 +0200 Subject: [PATCH] Add MessageSendingOperations for JMS This commit adds a JMS implementation of MessageSendingOperations, allowing to send JMS messages using Spring's standard Messaging abstraction. MessagingMessageConverter is a standard JMS's MessageConverter that can convert Spring's Message to JMS message and vice versa. Existing infrastructure has been updated to use this implementation. Issue: SPR-11772 --- .../AbstractAdaptableMessageListener.java | 34 ++- .../MessagingMessageListenerAdapter.java | 16 +- .../JmsMessageSendingOperations.java | 92 +++++++ .../jms/messaging/JmsMessagingTemplate.java | 206 +++++++++++++++ .../jms/messaging/package-info.java | 20 ++ .../converter/MessagingMessageConverter.java | 116 ++++++++ .../messaging/JmsMessagingTemplateTests.java | 250 ++++++++++++++++++ .../MessagingMessageConverterTests.java | 97 +++++++ .../core/AbstractMessageSendingTemplate.java | 19 +- .../core/MessageSendingOperations.java | 4 +- src/asciidoc/index.adoc | 4 + 11 files changed, 831 insertions(+), 27 deletions(-) create mode 100644 spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageSendingOperations.java create mode 100644 spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessagingTemplate.java create mode 100644 spring-jms/src/main/java/org/springframework/jms/messaging/package-info.java create mode 100644 spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java create mode 100644 spring-jms/src/test/java/org/springframework/jms/messaging/JmsMessagingTemplateTests.java create mode 100644 spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java index 3dd3a1b99ab..39cddf9f76c 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java @@ -32,6 +32,7 @@ import org.springframework.jms.support.JmsUtils; import org.springframework.jms.support.converter.JmsHeaderMapper; 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.jms.support.destination.DestinationResolver; @@ -60,7 +61,7 @@ public abstract class AbstractAdaptableMessageListener private MessageConverter messageConverter; - private JmsHeaderMapper headerMapper = new SimpleJmsHeaderMapper(); + private MessagingMessageConverterAdapter messagingMessageConverter = new MessagingMessageConverterAdapter(); /** @@ -152,6 +153,10 @@ public abstract class AbstractAdaptableMessageListener return this.messageConverter; } + protected MessageConverter getMessagingMessageConverter() { + return this.messagingMessageConverter; + } + /** * Set the {@link JmsHeaderMapper} implementation to use to map the * standard JMS headers. By default {@link SimpleJmsHeaderMapper} is @@ -160,15 +165,7 @@ public abstract class AbstractAdaptableMessageListener */ public void setHeaderMapper(JmsHeaderMapper headerMapper) { Assert.notNull(headerMapper, "HeaderMapper must not be null"); - this.headerMapper = headerMapper; - } - - /** - * Return the {@link JmsHeaderMapper} that converts headers from - * and to the messaging abstraction. - */ - protected JmsHeaderMapper getHeaderMapper() { - return headerMapper; + this.messagingMessageConverter.setHeaderMapper(headerMapper); } /** @@ -284,10 +281,7 @@ public abstract class AbstractAdaptableMessageListener MessageConverter converter = getMessageConverter(); if (converter != null) { if (result instanceof org.springframework.messaging.Message) { - org.springframework.messaging.Message message = (org.springframework.messaging.Message) result; - Message reply = converter.toMessage(message.getPayload(), session); - getHeaderMapper().fromHeaders(message.getHeaders(), reply); - return reply; + return messagingMessageConverter.toMessage(result, session); } else { return converter.toMessage(result, session); @@ -404,6 +398,18 @@ public abstract class AbstractAdaptableMessageListener } + /** + * Delegates payload extraction to {@link #extractMessage(javax.jms.Message)} to + * enforce backward compatibility. + */ + private class MessagingMessageConverterAdapter extends MessagingMessageConverter { + + @Override + protected Object extractPayload(Message message) throws JMSException { + return extractMessage(message); + } + } + /** * Internal class combining a destination name * and its target destination type (queue or topic). diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java index 77306fd2039..62a30ca3928 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java @@ -16,16 +16,14 @@ package org.springframework.jms.listener.adapter; -import java.util.Map; - import javax.jms.JMSException; import javax.jms.Session; import org.springframework.jms.support.converter.JmsHeaderMapper; +import org.springframework.jms.support.converter.MessageConversionException; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; -import org.springframework.messaging.support.MessageBuilder; /** * A {@link javax.jms.MessageListener} adapter that invokes a configurable @@ -75,12 +73,12 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis @SuppressWarnings("unchecked") protected Message toMessagingMessage(javax.jms.Message jmsMessage) { - Map mappedHeaders = getHeaderMapper().toHeaders(jmsMessage); - Object convertedObject = extractMessage(jmsMessage); - MessageBuilder builder = (convertedObject instanceof org.springframework.messaging.Message) ? - MessageBuilder.fromMessage((org.springframework.messaging.Message) convertedObject) : - MessageBuilder.withPayload(convertedObject); - return builder.copyHeadersIfAbsent(mappedHeaders).build(); + try { + return (Message) getMessagingMessageConverter().fromMessage(jmsMessage); + } + catch (JMSException e) { + throw new MessageConversionException("Could not unmarshal message", e); + } } /** diff --git a/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageSendingOperations.java b/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageSendingOperations.java new file mode 100644 index 00000000000..286db80e85d --- /dev/null +++ b/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessageSendingOperations.java @@ -0,0 +1,92 @@ +/* + * 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.jms.messaging; + +import java.util.Map; + +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.MessageSendingOperations; + +/** + * A specialization of {@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 { + + /** + * Send a message to the given destination. + * @param destinationName the name of the target destination + * @param message the message to send + */ + void send(String destinationName, Message message) throws MessagingException; + + /** + * Convert the given Object to serialized form, possibly using a + * {@link org.springframework.messaging.converter.MessageConverter}, + * wrap it as a message and send it to the given destination. + * @param destinationName the name of the target destination + * @param payload the Object to use as payload + */ + void convertAndSend(String destinationName, Object payload) throws MessagingException; + + /** + * Convert the given Object to serialized form, possibly using a + * {@link org.springframework.messaging.converter.MessageConverter}, + * wrap it as a message with the given headers and send it to + * the given destination. + * @param destinationName the name of the target destination + * @param payload the Object to use as payload + * @param headers headers for the message to send + */ + void convertAndSend(String destinationName, Object payload, Map headers) + throws MessagingException; + + /** + * Convert the given Object to serialized form, possibly using a + * {@link org.springframework.messaging.converter.MessageConverter}, + * wrap it as a message, apply the given post processor, and send + * the resulting message to the given destination. + * @param destinationName the name of the target destination + * @param payload the Object to use as payload + * @param postProcessor the post processor to apply to the message + */ + void convertAndSend(String destinationName, Object payload, MessagePostProcessor postProcessor) + throws MessagingException; + + /** + * Convert the given Object to serialized form, possibly using a + * {@link org.springframework.messaging.converter.MessageConverter}, + * wrap it as a message with the given headers, apply the given post processor, + * and send the resulting message to the given destination. + * @param destinationName the name of the target destination + * @param payload the Object to use as payload + * @param headers headers for the message to send + * @param postProcessor the post processor to apply to the message + */ + void convertAndSend(String destinationName, Object payload, Map headers, MessagePostProcessor postProcessor) 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 new file mode 100644 index 00000000000..25bac284da5 --- /dev/null +++ b/spring-jms/src/main/java/org/springframework/jms/messaging/JmsMessagingTemplate.java @@ -0,0 +1,206 @@ +/* + * 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.jms.messaging; + +import java.util.Map; + +import javax.jms.Destination; +import javax.jms.JMSException; +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.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.MessagePostProcessor; +import org.springframework.util.Assert; + +/** + * An implementation of {@link JmsMessageSendingOperations}. + * + * @author Stephane Nicoll + * @since 4.1 + */ +public class JmsMessagingTemplate + extends AbstractMessageSendingTemplate + implements JmsMessageSendingOperations, InitializingBean { + + private JmsTemplate jmsTemplate; + + private MessageConverter jmsMessageConverter = new MessagingMessageConverter( + new SimpleMessageConverter(), new SimpleJmsHeaderMapper()); + + private String defaultDestinationName; + + public JmsMessagingTemplate() { + } + + /** + * Create an instance with the {@link JmsTemplate} to use. + */ + public JmsMessagingTemplate(JmsTemplate jmsTemplate) { + Assert.notNull("JmsTemplate must not be null"); + this.jmsTemplate = jmsTemplate; + } + + /** + * Set the {@link JmsTemplate} to use. + */ + public void setJmsTemplate(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + /** + * Set the {@link MessageConverter} to use to convert a {@link Message} from + * the messaging to and from a {@link javax.jms.Message}. By default, a + * {@link MessagingMessageConverter} is defined using a {@link SimpleMessageConverter} + * to convert the payload of the message. + *

Consider configuring a {@link MessagingMessageConverter} with a different + * {@link MessagingMessageConverter#setPayloadConverter(MessageConverter) payload converter} + * for more advanced scenario. + * + * @see org.springframework.jms.support.converter.MessagingMessageConverter + */ + public void setJmsMessageConverter(MessageConverter jmsMessageConverter) { + this.jmsMessageConverter = jmsMessageConverter; + } + + /** + * Configure the default destination name to use in send methods that don't have + * a destination argument. If a default destination is not configured, send methods + * without a destination argument will raise an exception if invoked. + * @see #setDefaultDestination(Object) + */ + public void setDefaultDestinationName(String defaultDestinationName) { + this.defaultDestinationName = defaultDestinationName; + } + + /** + * Return the configured default destination name. + */ + public String getDefaultDestinationName() { + return this.defaultDestinationName; + } + + @Override + public void afterPropertiesSet() { + Assert.notNull(this.jmsTemplate, "Property 'jmsTemplate' is required"); + Assert.notNull(this.jmsMessageConverter, "Property 'jmsMessageConverter' is required"); + } + + + @Override + public void send(Message message) { + Destination defaultDestination = getDefaultDestination(); + if (defaultDestination != null) { + send(defaultDestination, message); + } + else { + send(getRequiredDefaultDestinationName(), message); + } + } + + @Override + public void convertAndSend(Object payload) throws MessagingException { + convertAndSend(payload, null); + } + + @Override + public void convertAndSend(Object payload, MessagePostProcessor postProcessor) throws MessagingException { + Destination defaultDestination = getDefaultDestination(); + if (defaultDestination != null) { + convertAndSend(defaultDestination, payload, postProcessor); + } + else { + convertAndSend(getRequiredDefaultDestinationName(), payload, postProcessor); + } + } + + @Override + public void send(String destinationName, Message message) throws MessagingException { + doSend(destinationName, message); + } + + @Override + public void convertAndSend(String destinationName, Object payload) throws MessagingException { + convertAndSend(destinationName, payload, (Map) null); + } + + @Override + public void convertAndSend(String destinationName, Object payload, Map headers) + throws MessagingException { + convertAndSend(destinationName, payload, headers, null); + } + + @Override + public void convertAndSend(String destinationName, Object payload, MessagePostProcessor postProcessor) + throws MessagingException { + convertAndSend(destinationName, payload, null, postProcessor); + } + + @Override + public void convertAndSend(String destinationName, Object payload, Map headers, + MessagePostProcessor postProcessor) throws MessagingException { + Message message = doConvert(payload, headers, postProcessor); + send(destinationName, message); + } + + @Override + protected void doSend(Destination destination, Message message) { + jmsTemplate.send(destination, new MessagingMessageCreator(message, this.jmsMessageConverter)); + } + + protected void doSend(String destinationName, Message message) { + jmsTemplate.send(destinationName, new MessagingMessageCreator(message, this.jmsMessageConverter)); + } + + protected String getRequiredDefaultDestinationName() { + String name = getDefaultDestinationName(); + if (name == null) { + throw new IllegalStateException( + "No 'defaultDestination' or 'defaultDestinationName' specified. " + + "Check configuration of JmsMessagingTemplate." + ); + } + return name; + } + + + private static class MessagingMessageCreator implements MessageCreator { + + private final Message message; + + private final MessageConverter messageConverter; + + private MessagingMessageCreator(Message message, MessageConverter messageConverter) { + this.message = message; + this.messageConverter = messageConverter; + } + + @Override + public javax.jms.Message createMessage(Session session) throws JMSException { + return messageConverter.toMessage(message, session); + } + } + +} diff --git a/spring-jms/src/main/java/org/springframework/jms/messaging/package-info.java b/spring-jms/src/main/java/org/springframework/jms/messaging/package-info.java new file mode 100644 index 00000000000..3d5f57cf5ac --- /dev/null +++ b/spring-jms/src/main/java/org/springframework/jms/messaging/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * JMS integration for Spring's messaging module. + */ +package org.springframework.jms.messaging; \ No newline at end of file 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 new file mode 100644 index 00000000000..e3206284dc7 --- /dev/null +++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java @@ -0,0 +1,116 @@ +/* + * 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.jms.support.converter; + +import java.util.Map; + +import javax.jms.JMSException; +import javax.jms.Session; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.util.Assert; + +/** + * Convert a {@link Message} from the messaging abstraction to + * and from a {@link javax.jms.Message} using an underlying + * {@link MessageConverter} for the payload and a {@link JmsHeaderMapper} + * to map the JMS headers to and from standard message headers. + * + * @author Stephane Nicoll + * @since 4.1 + */ +public class MessagingMessageConverter implements MessageConverter, InitializingBean { + + private MessageConverter payloadConverter; + + private JmsHeaderMapper headerMapper; + + /** + * Create an instance with a default payload converter. + * @see org.springframework.jms.support.converter.SimpleMessageConverter + * @see org.springframework.jms.support.converter.SimpleJmsHeaderMapper + */ + public MessagingMessageConverter() { + this(new SimpleMessageConverter(), new SimpleJmsHeaderMapper()); + } + + /** + * Create an instance with the specified payload converter and + * header mapper. + */ + public MessagingMessageConverter(MessageConverter payloadConverter, JmsHeaderMapper headerMapper) { + Assert.notNull(payloadConverter, "PayloadConverter must not be null"); + Assert.notNull(headerMapper, "HeaderMapper must not be null"); + this.payloadConverter = payloadConverter; + this.headerMapper = headerMapper; + } + + /** + * Set the {@link MessageConverter} to use to convert the payload. + */ + public void setPayloadConverter(MessageConverter payloadConverter) { + this.payloadConverter = payloadConverter; + } + + /** + * Set the {@link JmsHeaderMapper} to use to map JMS headers to and from + * standard message headers. + */ + public void setHeaderMapper(JmsHeaderMapper headerMapper) { + this.headerMapper = headerMapper; + } + + @Override + public void afterPropertiesSet() { + Assert.notNull(this.payloadConverter, "Property 'payloadConverter' is required"); + Assert.notNull(this.headerMapper, "Property 'headerMapper' is required"); + } + + @Override + public javax.jms.Message toMessage(Object object, Session session) throws JMSException, MessageConversionException { + if (!(object instanceof Message)) { + throw new IllegalArgumentException("Could not convert [" + object + "] only [" + + Message.class.getName() + "] is handled by this converter"); + } + + Message input = (Message) object; + javax.jms.Message reply = this.payloadConverter.toMessage(input.getPayload(), session); + this.headerMapper.fromHeaders(input.getHeaders(), reply); + return reply; + } + + @SuppressWarnings("unchecked") + @Override + public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException { + Map mappedHeaders = this.headerMapper.toHeaders(message); + Object convertedObject = extractPayload(message); + MessageBuilder builder = (convertedObject instanceof org.springframework.messaging.Message) ? + MessageBuilder.fromMessage((org.springframework.messaging.Message) convertedObject) : + MessageBuilder.withPayload(convertedObject); + return builder.copyHeadersIfAbsent(mappedHeaders).build(); + } + + /** + * Extract the payload of the specified {@link javax.jms.Message} + */ + protected Object extractPayload(javax.jms.Message message) throws JMSException { + return this.payloadConverter.fromMessage(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 new file mode 100644 index 00000000000..30e145ec22b --- /dev/null +++ b/spring-jms/src/test/java/org/springframework/jms/messaging/JmsMessagingTemplateTests.java @@ -0,0 +1,250 @@ +/* + * 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.jms.messaging; + +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.mock; + +import java.util.HashMap; +import java.util.Map; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +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.support.MessageBuilder; + +/** + * + * @author Stephane Nicoll + */ +public class JmsMessagingTemplateTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Captor + private ArgumentCaptor messageCreator; + + @Mock + private JmsTemplate jmsTemplate; + + private JmsMessagingTemplate messagingTemplate; + + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + messagingTemplate = new JmsMessagingTemplate(jmsTemplate); + } + + @Test + public void send() { + Destination destination = new Destination() {}; + Message message = createTextMessage(); + + messagingTemplate.send(destination, message); + verify(jmsTemplate).send(eq(destination), messageCreator.capture()); + assertTextMessage(messageCreator.getValue()); + } + + @Test + public void sendName() { + Message message = createTextMessage(); + + messagingTemplate.send("myQueue", message); + verify(jmsTemplate).send(eq("myQueue"), messageCreator.capture()); + assertTextMessage(messageCreator.getValue()); + } + + @Test + public void sendDefaultDestination() { + Destination destination = new Destination() {}; + messagingTemplate.setDefaultDestination(destination); + Message message = createTextMessage(); + + messagingTemplate.send(message); + verify(jmsTemplate).send(eq(destination), messageCreator.capture()); + assertTextMessage(messageCreator.getValue()); + } + + @Test + public void sendDefaultDestinationName() { + messagingTemplate.setDefaultDestinationName("myQueue"); + Message message = createTextMessage(); + + messagingTemplate.send(message); + verify(jmsTemplate).send(eq("myQueue"), messageCreator.capture()); + assertTextMessage(messageCreator.getValue()); + } + + @Test + public void sendNoDefaultSet() { + Message message = createTextMessage(); + + thrown.expect(IllegalStateException.class); + messagingTemplate.send(message); + } + + @Test + public void sendPropertyInjection() { + JmsMessagingTemplate t = new JmsMessagingTemplate(); + t.setJmsTemplate(jmsTemplate); + t.setDefaultDestinationName("myQueue"); + t.afterPropertiesSet(); + Message message = createTextMessage(); + + t.send(message); + verify(jmsTemplate).send(eq("myQueue"), messageCreator.capture()); + assertTextMessage(messageCreator.getValue()); + } + + @Test + public void convertAndSendPayload() throws JMSException { + Destination destination = new Destination() {}; + + messagingTemplate.convertAndSend(destination, "my Payload"); + verify(jmsTemplate).send(eq(destination), messageCreator.capture()); + TextMessage textMessage = createTextMessage(messageCreator.getValue()); + assertEquals("my Payload", textMessage.getText()); + } + + @Test + public void convertAndSendPayloadName() throws JMSException { + messagingTemplate.convertAndSend("myQueue", "my Payload"); + verify(jmsTemplate).send(eq("myQueue"), messageCreator.capture()); + TextMessage textMessage = createTextMessage(messageCreator.getValue()); + assertEquals("my Payload", textMessage.getText()); + } + + @Test + public void convertAndSendDefaultDestination() throws JMSException { + Destination destination = new Destination() {}; + messagingTemplate.setDefaultDestination(destination); + + messagingTemplate.convertAndSend("my Payload"); + verify(jmsTemplate).send(eq(destination), messageCreator.capture()); + TextMessage textMessage = createTextMessage(messageCreator.getValue()); + assertEquals("my Payload", textMessage.getText()); + } + + @Test + public void convertAndSendDefaultDestinationName() throws JMSException { + messagingTemplate.setDefaultDestinationName("myQueue"); + + messagingTemplate.convertAndSend("my Payload"); + verify(jmsTemplate).send(eq("myQueue"), messageCreator.capture()); + TextMessage textMessage = createTextMessage(messageCreator.getValue()); + assertEquals("my Payload", textMessage.getText()); + } + + @Test + public void convertAndSendNoDefaultSet() throws JMSException { + thrown.expect(IllegalStateException.class); + messagingTemplate.convertAndSend("my Payload"); + } + + @Test + public void convertAndSendCustomJmsMessageConverter() throws JMSException { + messagingTemplate.setJmsMessageConverter(new SimpleMessageConverter() { + @Override + public javax.jms.Message toMessage(Object object, Session session) + throws JMSException, MessageConversionException { + throw new MessageConversionException("Test exception"); + } + }); + + messagingTemplate.convertAndSend("myQueue", "msg to convert"); + verify(jmsTemplate).send(eq("myQueue"), messageCreator.capture()); + + thrown.expect(MessageConversionException.class); + thrown.expectMessage("Test exception"); + messageCreator.getValue().createMessage(mock(Session.class)); + } + + @Test + public void convertAndSendPayloadAndHeaders() throws JMSException { + Destination destination = new Destination() {}; + Map headers = new HashMap(); + headers.put("foo", "bar"); + + messagingTemplate.convertAndSend(destination, "Hello", headers); + verify(jmsTemplate).send(eq(destination), messageCreator.capture()); + assertTextMessage(messageCreator.getValue()); // see createTextMessage + } + + @Test + public void convertAndSendPayloadAndHeadersName() throws JMSException { + Map headers = new HashMap(); + headers.put("foo", "bar"); + + messagingTemplate.convertAndSend("myQueue", "Hello", headers); + verify(jmsTemplate).send(eq("myQueue"), messageCreator.capture()); + assertTextMessage(messageCreator.getValue()); // see createTextMessage + } + + + private Message createTextMessage() { + return MessageBuilder + .withPayload("Hello").setHeader("foo", "bar").build(); + } + + private void assertTextMessage(MessageCreator messageCreator) { + try { + TextMessage jmsMessage = createTextMessage(messageCreator); + assertEquals("Wrong body message", "Hello", jmsMessage.getText()); + assertEquals("Invalid foo property", "bar", jmsMessage.getStringProperty("foo")); + } + catch (JMSException e) { + throw new IllegalStateException("Wrong text message", e); + } + } + + + protected TextMessage createTextMessage(MessageCreator creator) throws JMSException { + Session mock = mock(Session.class); + given(mock.createTextMessage(any())).willAnswer(new Answer() { + @Override + public TextMessage answer(InvocationOnMock invocation) throws Throwable { + return new StubTextMessage((String) invocation.getArguments()[0]); + } + }); + javax.jms.Message message = creator.createMessage(mock); + verify(mock).createTextMessage(any()); + return TextMessage.class.cast(message); + } + +} 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 new file mode 100644 index 00000000000..02716006ff8 --- /dev/null +++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/MessagingMessageConverterTests.java @@ -0,0 +1,97 @@ +/* + * 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.jms.support.converter; + +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.Serializable; + +import javax.jms.JMSException; +import javax.jms.ObjectMessage; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.jms.StubTextMessage; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; + +/** + * + * @author Stephane Nicoll + */ +public class MessagingMessageConverterTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private final MessagingMessageConverter converter = new MessagingMessageConverter(); + + @Test + public void onlyHandlesMessage() throws JMSException { + thrown.expect(IllegalArgumentException.class); + converter.toMessage(new Object(), mock(Session.class)); + } + + @Test + public void simpleObject() throws Exception { + Session session = mock(Session.class); + Serializable payload = mock(Serializable.class); + ObjectMessage jmsMessage = mock(ObjectMessage.class); + given(session.createObjectMessage(payload)).willReturn(jmsMessage); + + converter.toMessage(MessageBuilder.withPayload(payload).build(), session); + verify(session).createObjectMessage(payload); + } + + @Test + public void customPayloadConverter() throws JMSException { + TextMessage jmsMsg = new StubTextMessage("1224"); + + converter.setPayloadConverter(new SimpleMessageConverter() { + @Override + public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException { + TextMessage textMessage = (TextMessage) message; + return Long.parseLong(textMessage.getText()); + } + }); + + Message msg = (Message) converter.fromMessage(jmsMsg); + assertEquals(1224L, msg.getPayload()); + } + + @Test + public void payloadIsAMessage() throws JMSException { + final Message message = MessageBuilder.withPayload("Test").setHeader("inside", true).build(); + converter.setPayloadConverter(new SimpleMessageConverter() { + @Override + public Object fromMessage(javax.jms.Message jmsMessage) throws JMSException, MessageConversionException { + return message; + } + }); + Message msg = (Message) converter.fromMessage(new StubTextMessage()); + assertEquals(message.getPayload(), msg.getPayload()); + assertEquals(true, msg.getHeaders().get("inside")); + } + +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageSendingTemplate.java b/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageSendingTemplate.java index c00bf48a8fb..aae1a736a02 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageSendingTemplate.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageSendingTemplate.java @@ -34,6 +34,7 @@ import org.springframework.util.Assert; * * @author Mark Fisher * @author Rossen Stoyanchev + * @author Stephane Nicoll * @since 4.0 */ public abstract class AbstractMessageSendingTemplate implements MessageSendingOperations { @@ -99,7 +100,7 @@ public abstract class AbstractMessageSendingTemplate implements MessageSendin @Override public void convertAndSend(Object payload) throws MessagingException { - convertAndSend(getRequiredDefaultDestination(), payload); + convertAndSend(payload, null); } @Override @@ -128,6 +129,20 @@ public abstract class AbstractMessageSendingTemplate implements MessageSendin public void convertAndSend(D destination, Object payload, Map headers, MessagePostProcessor postProcessor) throws MessagingException { + Message message = doConvert(payload, headers, postProcessor); + send(destination, message); + } + + /** + * Convert the given Object to serialized form, possibly using a + * {@link MessageConverter}, wrap it as a message with the given + * headers and apply the given post processor. + * @param payload the Object to use as payload + * @param headers headers for the message to send + * @param postProcessor the post processor to apply to the message + * @return the converted message + */ + protected Message doConvert(Object payload, Map headers, MessagePostProcessor postProcessor) { MessageHeaders messageHeaders = null; Map headersToUse = processHeadersToSend(headers); if (headersToUse != null) { @@ -149,7 +164,7 @@ public abstract class AbstractMessageSendingTemplate implements MessageSendin if (postProcessor != null) { message = postProcessor.postProcessMessage(message); } - send(destination, message); + return message; } /** diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/MessageSendingOperations.java b/spring-messaging/src/main/java/org/springframework/messaging/core/MessageSendingOperations.java index 5e08b4b2e5a..3f0797aca92 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/core/MessageSendingOperations.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/core/MessageSendingOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * 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. @@ -66,7 +66,7 @@ public interface MessageSendingOperations { * Convert the given Object to serialized form, possibly using a * {@link org.springframework.messaging.converter.MessageConverter}, * wrap it as a message with the given headers and send it to - * a default destination. + * the given destination. * @param destination the target destination * @param payload the Object to use as payload * @param headers headers for the message to send diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 127ff330fd0..754cd5785b8 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -40428,6 +40428,10 @@ clear, the `JmsTemplate` is stateful, in that it maintains a reference to a `ConnectionFactory`, but this state is __not__ conversational state. ==== +As of Spring Framework 4.1, `JmsMessagingTemplate` is built on top of `JmsTemplate` +and provides an integration with the messaging abstraction, i.e. +`org.springframework.messaging.Message`. This allows you to create the message to +send in generic manner. [[jms-connections]]