diff --git a/spring-jms/src/main/java/org/springframework/jms/core/DefaultJmsClient.java b/spring-jms/src/main/java/org/springframework/jms/core/DefaultJmsClient.java new file mode 100644 index 00000000000..fb9b2d89a65 --- /dev/null +++ b/spring-jms/src/main/java/org/springframework/jms/core/DefaultJmsClient.java @@ -0,0 +1,195 @@ +/* + * Copyright 2002-present 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 + * + * https://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.core; + +import java.util.Map; +import java.util.Optional; + +import jakarta.jms.ConnectionFactory; +import jakarta.jms.Destination; +import org.jspecify.annotations.Nullable; + +import org.springframework.jms.support.JmsAccessor; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.util.Assert; + +/** + * The default implementation of {@link JmsClient}, + * as created by the static factory methods. + * + * @author Juergen Hoeller + * @since 7.0 + * @see JmsClient#create(ConnectionFactory) + * @see JmsClient#create(ConnectionFactory, MessageConverter) + * @see JmsClient#create(JmsOperations) + * @see JmsClient#create(JmsOperations, MessageConverter) + */ +class DefaultJmsClient implements JmsClient { + + private final JmsOperations jmsTemplate; + + private final @Nullable MessageConverter messageConverter; + + + public DefaultJmsClient(ConnectionFactory connectionFactory, @Nullable MessageConverter messageConverter) { + this.jmsTemplate = new JmsTemplate(connectionFactory); + this.messageConverter = messageConverter; + } + + public DefaultJmsClient(JmsOperations jmsTemplate, @Nullable MessageConverter messageConverter) { + Assert.notNull(jmsTemplate, "JmsTemplate must not be null"); + this.jmsTemplate = jmsTemplate; + this.messageConverter = messageConverter; + } + + + public OperationSpec destination(Destination destination) { + return new DefaultOperationSpec(destination); + } + + public OperationSpec destination(String destinationName) { + return new DefaultOperationSpec(destinationName); + } + + private JmsMessagingTemplate newDelegate() { + JmsMessagingTemplate delegate = new JmsMessagingTemplate(DefaultJmsClient.this.jmsTemplate); + MessageConverter converter = DefaultJmsClient.this.messageConverter; + if (converter != null) { + delegate.setMessageConverter(converter); + } + return delegate; + } + + + private class DefaultOperationSpec implements OperationSpec { + + private final JmsMessagingTemplate delegate; + + private @Nullable JmsTemplate customTemplate; + + public DefaultOperationSpec(Destination destination) { + this.delegate = newDelegate(); + this.delegate.setDefaultDestination(destination); + } + + public DefaultOperationSpec(String destinationName) { + this.delegate = newDelegate(); + this.delegate.setDefaultDestinationName(destinationName); + } + + private JmsTemplate enforceCustomTemplate(boolean qos) { + if (this.customTemplate == null) { + JmsOperations jmsOperations = DefaultJmsClient.this.jmsTemplate; + if (!(jmsOperations instanceof JmsAccessor original)) { + throw new IllegalStateException( + "Needs to be bound to a JmsAccessor for custom settings support: " + jmsOperations); + } + this.customTemplate = new JmsTemplate(original); + this.delegate.setJmsTemplate(this.customTemplate); + } + if (qos) { + this.customTemplate.setExplicitQosEnabled(true); + } + return this.customTemplate; + } + + @Override + public OperationSpec withReceiveTimeout(long receiveTimeout) { + enforceCustomTemplate(false).setReceiveTimeout(receiveTimeout); + return this; + } + + @Override + public OperationSpec withDeliveryDelay(long deliveryDelay) { + enforceCustomTemplate(false).setDeliveryDelay(deliveryDelay); + return this; + } + + @Override + public OperationSpec withDeliveryPersistent(boolean persistent) { + enforceCustomTemplate(true).setDeliveryPersistent(persistent); + return this; + } + + @Override + public OperationSpec withPriority(int priority) { + enforceCustomTemplate(true).setPriority(priority); + return this; + } + + @Override + public OperationSpec withTimeToLive(long timeToLive) { + enforceCustomTemplate(true).setTimeToLive(timeToLive); + return this; + } + + @Override + public void send(Message message) throws MessagingException { + this.delegate.send(message); + } + + @Override + public void send(Object payload) throws MessagingException { + this.delegate.convertAndSend(payload); + } + + @Override + public void send(Object payload, Map headers) throws MessagingException { + this.delegate.convertAndSend(payload, headers); + } + + @Override + public Optional> receive() throws MessagingException { + return Optional.ofNullable(this.delegate.receive()); + } + + @Override + public Optional receive(Class targetClass) throws MessagingException { + return Optional.ofNullable(this.delegate.receiveAndConvert(targetClass)); + } + + @Override + public Optional> receive(String messageSelector) throws MessagingException { + return Optional.ofNullable(this.delegate.receiveSelected(messageSelector)); + } + + @Override + public Optional receive(String messageSelector, Class targetClass) throws MessagingException { + return Optional.ofNullable(this.delegate.receiveSelectedAndConvert(messageSelector, targetClass)); + } + + @Override + public Optional> sendAndReceive(Message requestMessage) throws MessagingException { + return Optional.ofNullable(this.delegate.sendAndReceive(requestMessage)); + } + + @Override + public Optional sendAndReceive(Object request, Class targetClass) throws MessagingException { + return Optional.ofNullable(this.delegate.convertSendAndReceive(request, targetClass)); + } + + @Override + public Optional sendAndReceive(Object request, Map headers, Class targetClass) + throws MessagingException { + + return Optional.ofNullable(this.delegate.convertSendAndReceive(request, headers, targetClass)); + } + } + +} diff --git a/spring-jms/src/main/java/org/springframework/jms/core/JmsClient.java b/spring-jms/src/main/java/org/springframework/jms/core/JmsClient.java new file mode 100644 index 00000000000..736380d2117 --- /dev/null +++ b/spring-jms/src/main/java/org/springframework/jms/core/JmsClient.java @@ -0,0 +1,248 @@ +/* + * Copyright 2002-present 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 + * + * https://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.core; + +import java.util.Map; +import java.util.Optional; + +import jakarta.jms.ConnectionFactory; +import jakarta.jms.Destination; + +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.converter.MessageConverter; + +/** + * A fluent {@code JmsClient} with common send and receive operations against a JMS + * destination. This is effectively an alternative to {@link JmsMessagingTemplate}, + * also delegating to Spring's {@link JmsTemplate} for performing actual operations. + * + *

Note: Operations in this interface throw {@link MessagingException} instead of + * the JMS-specific {@link org.springframework.jms.JmsException}, aligning with the + * {@code spring-messaging} module and its other client operation handles. + * + *

This client provides reusable operation handles which can be configured with + * custom QoS settings. Note that any use of such explicit settings will override + * administrative provider settings (see {@link JmsTemplate#setExplicitQosEnabled}). + * + * @author Juergen Hoeller + * @since 7.0 + * @see JmsTemplate + * @see JmsMessagingTemplate + */ +interface JmsClient { + + /** + * Provide an operation handle for the given JMS destination. + * @param destination the JMS {@code Destination} object + * @return a reusable operation handle bound to the destination + */ + OperationSpec destination(Destination destination); + + /** + * Provide an operation handle for the specified JMS destination. + * @param destinationName a name resolving to a JMS {@code Destination} + * @return a reusable operation handle bound to the destination + * @see org.springframework.jms.support.destination.DestinationResolver + */ + OperationSpec destination(String destinationName); + + + // Static factory methods + + /** + * Create a new {@code JmsClient} for the given {@link ConnectionFactory}. + * @param connectionFactory the factory to obtain JMS connections from + */ + static JmsClient create(ConnectionFactory connectionFactory) { + return new DefaultJmsClient(connectionFactory, null); + } + + /** + * Create a new {@code JmsClient} for the given {@link ConnectionFactory}. + * @param connectionFactory the factory to obtain JMS connections from + * @param messageConverter the message converter for payload objects + */ + static JmsClient create(ConnectionFactory connectionFactory, MessageConverter messageConverter) { + return new DefaultJmsClient(connectionFactory, messageConverter); + } + + /** + * Create a new {@code JmsClient} for the given {@link ConnectionFactory}. + * @param jmsTemplate the {@link JmsTemplate} to use for performing operations + * (can be a custom {@link JmsOperations} implementation as well) + */ + static JmsClient create(JmsOperations jmsTemplate) { + return new DefaultJmsClient(jmsTemplate, null); + } + + /** + * Create a new {@code JmsClient} for the given {@link ConnectionFactory}. + * @param jmsTemplate the {@link JmsTemplate} to use for performing operations + * (can be a custom {@link JmsOperations} implementation as well) + * @param messageConverter the message converter for payload objects + */ + static JmsClient create(JmsOperations jmsTemplate, MessageConverter messageConverter) { + return new DefaultJmsClient(jmsTemplate, messageConverter); + } + + + /** + * Common JMS send and receive operations with various settings. + */ + interface OperationSpec { + + /** + * Apply the given timeout to any subsequent receive operations. + * @param receiveTimeout the timeout in milliseconds + * @see JmsTemplate#setReceiveTimeout + */ + OperationSpec withReceiveTimeout(long receiveTimeout); + + /** + * Apply the given delivery delay to any subsequent send operations. + * @param deliveryDelay the delay in milliseconds + * @see JmsTemplate#setDeliveryDelay + */ + OperationSpec withDeliveryDelay(long deliveryDelay); + + /** + * Set whether message delivery should be persistent or non-persistent. + * @param persistent to choose between delivery mode "PERSISTENT" + * ({@code true}) or "NON_PERSISTENT" ({@code false}) + * @see JmsTemplate#setDeliveryPersistent + */ + OperationSpec withDeliveryPersistent(boolean persistent); + + /** + * Apply the given priority to any subsequent send operations. + * @param priority the priority value + * @see JmsTemplate#setPriority + */ + OperationSpec withPriority(int priority); + + /** + * Apply the given time-to-live to any subsequent send operations. + * @param timeToLive the message lifetime in milliseconds + * @see JmsTemplate#setTimeToLive + */ + OperationSpec withTimeToLive(long timeToLive); + + /** + * Send the given {@link Message} to the pre-bound destination. + * @param message the spring-messaging {@link Message} to send + * @see #withDeliveryDelay + * @see #withDeliveryPersistent + * @see #withPriority + * @see #withTimeToLive + */ + void send(Message message) throws MessagingException; + + /** + * Send a message with the given payload to the pre-bound destination. + * @param payload the payload to convert into a {@link Message} + * @see #withDeliveryDelay + * @see #withDeliveryPersistent + * @see #withPriority + * @see #withTimeToLive + */ + void send(Object payload) throws MessagingException; + + /** + * Send a message with the given payload to the pre-bound destination. + * @param payload the payload to convert into a {@link Message} + * @param headers the message headers to apply to the {@link Message} + * @see #withDeliveryDelay + * @see #withDeliveryPersistent + * @see #withPriority + * @see #withTimeToLive + */ + void send(Object payload, Map headers) throws MessagingException; + + /** + * Receive a {@link Message} from the pre-bound destination. + * @return the spring-messaging {@link Message} received, + * or {@link Optional#empty()} if none + * @see #withReceiveTimeout + */ + Optional> receive() throws MessagingException; + + /** + * Receive a {@link Message} from the pre-bound destination, + * extracting and converting its payload. + * @param targetClass the class to convert the payload to + * @return the payload of the {@link Message} received, + * or {@link Optional#empty()} if none + * @see #withReceiveTimeout + */ + Optional receive(Class targetClass) throws MessagingException; + + /** + * Receive a {@link Message} from the pre-bound destination. + * @param messageSelector the JMS message selector to apply + * @return the spring-messaging {@link Message} received, + * or {@link Optional#empty()} if none + * @see #withReceiveTimeout + */ + Optional> receive(String messageSelector) throws MessagingException; + + /** + * Receive a {@link Message} from the pre-bound destination, + * extracting and converting its payload. + * @param targetClass the class to convert the payload to + * @return the payload of the {@link Message} received, + * or {@link Optional#empty()} if none + * @param messageSelector the JMS message selector to apply + * @see #withReceiveTimeout + */ + Optional receive(String messageSelector, Class targetClass) throws MessagingException; + + /** + * Send a request message and receive the reply from the given destination. + * @param requestMessage the spring-messaging {@link Message} to send + * @return the spring-messaging {@link Message} received as a reply, + * or {@link Optional#empty()} if none + * @see #withReceiveTimeout + */ + Optional> sendAndReceive(Message requestMessage) throws MessagingException; + + /** + * Send a request message and receive the reply from the given destination. + * @param request the payload to convert into a request {@link Message} + * @param targetClass the class to convert the reply's payload to + * @return the payload of the {@link Message} received as a reply, + * or {@link Optional#empty()} if none + * @see #withTimeToLive + * @see #withReceiveTimeout + */ + Optional sendAndReceive(Object request, Class targetClass) throws MessagingException; + + /** + * Send a request message and receive the reply from the given destination. + * @param request the payload to convert into a request {@link Message} + * @param headers the message headers to apply to the request {@link Message} + * @param targetClass the class to convert the reply's payload to + * @return the payload of the {@link Message} received as a reply, + * or {@link Optional#empty()} if none + * @see #withTimeToLive + * @see #withReceiveTimeout + */ + Optional sendAndReceive(Object request, Map headers, Class targetClass) + throws MessagingException; + } + +} diff --git a/spring-jms/src/main/java/org/springframework/jms/core/JmsMessageOperations.java b/spring-jms/src/main/java/org/springframework/jms/core/JmsMessageOperations.java index c2a0e123cd3..44b8ae2fc76 100644 --- a/spring-jms/src/main/java/org/springframework/jms/core/JmsMessageOperations.java +++ b/spring-jms/src/main/java/org/springframework/jms/core/JmsMessageOperations.java @@ -30,10 +30,15 @@ import org.springframework.messaging.core.MessageSendingOperations; /** * A specialization of {@link MessageSendingOperations}, {@link MessageReceivingOperations} - * and {@link MessageRequestReplyOperations} for JMS related operations that allow to specify - * a destination name rather than the actual {@link jakarta.jms.Destination}. + * and {@link MessageRequestReplyOperations} for JMS related operations that allow to + * specify a destination name rather than the actual {@link jakarta.jms.Destination}. + * + *

Note: Operations in this interface throw {@link MessagingException} instead of + * the JMS-specific {@link org.springframework.jms.JmsException}, aligning with the + * {@code spring-messaging} module and its other client operation handles. * * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.1 * @see org.springframework.jms.core.JmsTemplate * @see org.springframework.messaging.core.MessageSendingOperations @@ -68,30 +73,30 @@ public interface JmsMessageOperations extends MessageSendingOperations headers) + void convertAndSend(String destinationName, Object payload, @Nullable 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 + * 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 + * @param postProcessor the post-processor to apply to the message */ - void convertAndSend(String destinationName, Object payload, MessagePostProcessor postProcessor) + void convertAndSend(String destinationName, Object payload, @Nullable 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, + * 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 the headers for the message to send - * @param postProcessor the post processor to apply to the message + * @param postProcessor the post-processor to apply to the message */ void convertAndSend(String destinationName, Object payload, @Nullable Map headers, @Nullable MessagePostProcessor postProcessor) throws MessagingException; @@ -114,6 +119,81 @@ public interface JmsMessageOperations extends MessageSendingOperations @Nullable T receiveAndConvert(String destinationName, Class targetClass) throws MessagingException; + /** + * Receive a message from the default destination. + * @param messageSelector the JMS message selector expression (or {@code null} if none). + * See the JMS specification for a detailed definition of selector expressions. + * @return the received message, possibly {@code null} if the message could not + * be received, for example due to a timeout + * @since 7.0 + */ + @Nullable Message receiveSelected(@Nullable String messageSelector) throws MessagingException; + + /** + * Receive a message from the given destination. + * @param destination the target destination + * @param messageSelector the JMS message selector expression (or {@code null} if none). + * See the JMS specification for a detailed definition of selector expressions. + * @return the received message, possibly {@code null} if the message could not + * be received, for example due to a timeout + * @since 7.0 + */ + @Nullable Message receiveSelected(Destination destination, @Nullable String messageSelector) + throws MessagingException; + + /** + * Receive a message from the given destination. + * @param destinationName the name of the target destination + * @param messageSelector the JMS message selector expression (or {@code null} if none). + * See the JMS specification for a detailed definition of selector expressions. + * @return the received message, possibly {@code null} if the message could not + * be received, for example due to a timeout + * @since 7.0 + */ + @Nullable Message receiveSelected(String destinationName, @Nullable String messageSelector) + throws MessagingException; + + /** + * Receive a message from the default destination and convert its payload to the + * specified target class. + * @param messageSelector the JMS message selector expression (or {@code null} if none). + * See the JMS specification for a detailed definition of selector expressions. + * @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 + * @since 7.0 + */ + @Nullable T receiveSelectedAndConvert(@Nullable String messageSelector, Class targetClass) + throws MessagingException; + + /** + * Receive a message from the given destination and convert its payload to the + * specified target class. + * @param destination the target destination + * @param messageSelector the JMS message selector expression (or {@code null} if none). + * See the JMS specification for a detailed definition of selector expressions. + * @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 + * @since 7.0 + */ + @Nullable T receiveSelectedAndConvert(Destination destination, @Nullable String messageSelector, + Class targetClass) 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 messageSelector the JMS message selector expression (or {@code null} if none). + * See the JMS specification for a detailed definition of selector expressions. + * @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 + * @since 7.0 + */ + @Nullable T receiveSelectedAndConvert(String destinationName, @Nullable String messageSelector, + Class targetClass) throws MessagingException; + /** * Send a request message and receive the reply from the given destination. * @param destinationName the name of the target destination @@ -121,7 +201,8 @@ public interface JmsMessageOperations extends MessageSendingOperations sendAndReceive(String destinationName, Message requestMessage) throws MessagingException; + @Nullable Message sendAndReceive(String destinationName, Message requestMessage) + throws MessagingException; /** * Convert the given request Object to serialized form, possibly using a @@ -134,7 +215,8 @@ public interface JmsMessageOperations extends MessageSendingOperations @Nullable T convertSendAndReceive(String destinationName, Object request, Class targetClass) throws MessagingException; + @Nullable T convertSendAndReceive(String destinationName, Object request, Class targetClass) + throws MessagingException; /** * Convert the given request Object to serialized form, possibly using a @@ -148,13 +230,13 @@ public interface JmsMessageOperations extends MessageSendingOperations @Nullable T convertSendAndReceive(String destinationName, Object request, @Nullable Map headers, Class targetClass) - throws MessagingException; + @Nullable T convertSendAndReceive(String destinationName, Object request, + @Nullable Map headers, Class targetClass) throws MessagingException; /** * Convert the given request Object to serialized form, possibly using a * {@link org.springframework.messaging.converter.MessageConverter}, - * apply the given post processor and send the resulting {@link Message} to the + * apply the given post-processor and send the resulting {@link Message} to the * given destination, receive the reply and convert its body of the given * target class. * @param destinationName the name of the target destination @@ -165,12 +247,12 @@ public interface JmsMessageOperations extends MessageSendingOperations @Nullable T convertSendAndReceive(String destinationName, Object request, Class targetClass, - MessagePostProcessor requestPostProcessor) throws MessagingException; + @Nullable MessagePostProcessor requestPostProcessor) throws MessagingException; /** * Convert the given request 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 + * wrap it as a message with the given headers, apply the given post-processor * and send the resulting {@link Message} to the specified destination, receive * the reply and convert its body of the given target class. * @param destinationName the name of the target destination @@ -180,7 +262,8 @@ public interface JmsMessageOperations extends MessageSendingOperations @Nullable T convertSendAndReceive(String destinationName, Object request, Map headers, - Class targetClass, MessagePostProcessor requestPostProcessor) throws MessagingException; + @Nullable T convertSendAndReceive(String destinationName, Object request, + @Nullable Map headers, Class targetClass, + @Nullable MessagePostProcessor requestPostProcessor) throws MessagingException; } diff --git a/spring-jms/src/main/java/org/springframework/jms/core/JmsMessagingTemplate.java b/spring-jms/src/main/java/org/springframework/jms/core/JmsMessagingTemplate.java index 83a134ec265..f644f7aba0d 100644 --- a/spring-jms/src/main/java/org/springframework/jms/core/JmsMessagingTemplate.java +++ b/spring-jms/src/main/java/org/springframework/jms/core/JmsMessagingTemplate.java @@ -27,6 +27,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.jms.InvalidDestinationException; import org.springframework.jms.JmsException; +import org.springframework.jms.support.JmsAccessor; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.converter.MessagingMessageConverter; import org.springframework.jms.support.converter.SimpleMessageConverter; @@ -39,16 +40,24 @@ import org.springframework.messaging.core.MessagePostProcessor; import org.springframework.util.Assert; /** - * An implementation of {@link JmsMessageOperations}. + * An implementation of {@link JmsMessageOperations}, as a wrapper on top of Spring's + * traditional {@link JmsTemplate}. Aligned with the {@code spring-messaging} module + * and {@link org.springframework.messaging.core.GenericMessagingTemplate}. + * + *

Note: Operations in this interface throw {@link MessagingException} instead of + * the JMS-specific {@link org.springframework.jms.JmsException}, aligning with the + * {@code spring-messaging} module and its other client operation handles. * * @author Stephane Nicoll * @author Juergen Hoeller * @since 4.1 + * @see JmsTemplate + * @see JmsClient */ public class JmsMessagingTemplate extends AbstractMessagingTemplate implements JmsMessageOperations, InitializingBean { - private @Nullable JmsTemplate jmsTemplate; + private @Nullable JmsOperations jmsTemplate; private MessageConverter jmsMessageConverter = new MessagingMessageConverter(); @@ -74,21 +83,29 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } /** - * Create a {@code JmsMessagingTemplate} instance with the {@link JmsTemplate} to use. + * Create a {@code JmsMessagingTemplate} instance with the {@link JmsOperations} to use. + * @since 7.0 */ - public JmsMessagingTemplate(JmsTemplate jmsTemplate) { + public JmsMessagingTemplate(JmsOperations jmsTemplate) { Assert.notNull(jmsTemplate, "JmsTemplate must not be null"); this.jmsTemplate = jmsTemplate; } + /** + * Create a {@code JmsMessagingTemplate} instance with the {@link JmsTemplate} to use. + */ + public JmsMessagingTemplate(JmsTemplate jmsTemplate) { + this((JmsOperations) jmsTemplate); + } + /** * Set the ConnectionFactory to use for the underlying {@link JmsTemplate}. * @since 4.1.2 */ public void setConnectionFactory(ConnectionFactory connectionFactory) { - if (this.jmsTemplate != null) { - this.jmsTemplate.setConnectionFactory(connectionFactory); + if (this.jmsTemplate instanceof JmsAccessor template) { + template.setConnectionFactory(connectionFactory); } else { this.jmsTemplate = new JmsTemplate(connectionFactory); @@ -100,7 +117,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate * @since 4.1.2 */ public @Nullable ConnectionFactory getConnectionFactory() { - return (this.jmsTemplate != null ? this.jmsTemplate.getConnectionFactory() : null); + return (this.jmsTemplate instanceof JmsTemplate template ? template.getConnectionFactory() : null); } /** @@ -114,14 +131,14 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate * Return the configured {@link JmsTemplate}. */ public @Nullable JmsTemplate getJmsTemplate() { - return this.jmsTemplate; + return (this.jmsTemplate instanceof JmsTemplate template ? template : null); } /** - * Set the {@link MessageConverter} to use to convert a {@link Message} from - * the messaging to and from a {@link jakarta.jms.Message}. By default, a - * {@link MessagingMessageConverter} is defined using a {@link SimpleMessageConverter} - * to convert the payload of the message. + * Set the {@link MessageConverter} to use to convert a {@link Message} + * to and from a {@link jakarta.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 scenarios. @@ -135,7 +152,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate /** * Return the {@link MessageConverter} to use to convert a {@link Message} - * from the messaging to and from a {@link jakarta.jms.Message}. + * to and from a {@link jakarta.jms.Message}. */ public MessageConverter getJmsMessageConverter() { return this.jmsMessageConverter; @@ -161,20 +178,19 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate @Override public void afterPropertiesSet() { Assert.notNull(this.jmsTemplate, "Property 'connectionFactory' or 'jmsTemplate' is required"); - if (!this.converterSet && this.jmsTemplate.getMessageConverter() != null) { - ((MessagingMessageConverter) this.jmsMessageConverter) - .setPayloadConverter(this.jmsTemplate.getMessageConverter()); + if (!this.converterSet && this.jmsTemplate instanceof JmsTemplate template) { + ((MessagingMessageConverter) this.jmsMessageConverter).setPayloadConverter(template.getMessageConverter()); } } - private JmsTemplate obtainJmsTemplate() { + JmsOperations obtainJmsTemplate() { Assert.state(this.jmsTemplate != null, "No JmsTemplate set"); return this.jmsTemplate; } @Override - public void send(Message message) { + public void send(Message message) throws MessagingException { Destination defaultDestination = getDefaultDestination(); if (defaultDestination != null) { send(defaultDestination, message); @@ -185,13 +201,15 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } @Override - public void convertAndSend(Object payload, @Nullable MessagePostProcessor postProcessor) throws MessagingException { + public void convertAndSend(Object payload, @Nullable Map headers, + @Nullable MessagePostProcessor postProcessor) throws MessagingException { + Destination defaultDestination = getDefaultDestination(); if (defaultDestination != null) { - convertAndSend(defaultDestination, payload, postProcessor); + convertAndSend(defaultDestination, payload, headers, postProcessor); } else { - convertAndSend(getRequiredDefaultDestinationName(), payload, postProcessor); + convertAndSend(getRequiredDefaultDestinationName(), payload, headers, postProcessor); } } @@ -228,7 +246,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } @Override - public @Nullable Message receive() { + public @Nullable Message receive() throws MessagingException { Destination defaultDestination = getDefaultDestination(); if (defaultDestination != null) { return receive(defaultDestination); @@ -239,7 +257,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } @Override - public @Nullable T receiveAndConvert(Class targetClass) { + public @Nullable T receiveAndConvert(Class targetClass) throws MessagingException { Destination defaultDestination = getDefaultDestination(); if (defaultDestination != null) { return receiveAndConvert(defaultDestination, targetClass); @@ -266,7 +284,71 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } @Override - public @Nullable Message sendAndReceive(Message requestMessage) { + public @Nullable Message receiveSelected(@Nullable String messageSelector) throws MessagingException { + Destination defaultDestination = getDefaultDestination(); + if (defaultDestination != null) { + return receiveSelected(defaultDestination, messageSelector); + } + else { + return receiveSelected(getRequiredDefaultDestinationName(), messageSelector); + } + } + + @Override + public @Nullable Message receiveSelected(Destination destination, @Nullable String messageSelector) + throws MessagingException { + + return doReceiveSelected(destination, messageSelector); + } + + @Override + public @Nullable Message receiveSelected(String destinationName, @Nullable String messageSelector) + throws MessagingException { + + return doReceiveSelected(destinationName, messageSelector); + } + + @Override + public @Nullable T receiveSelectedAndConvert(@Nullable String messageSelector, Class targetClass) + throws MessagingException { + + Destination defaultDestination = getDefaultDestination(); + if (defaultDestination != null) { + return receiveSelectedAndConvert(defaultDestination, messageSelector, targetClass); + } + else { + return receiveSelectedAndConvert(getRequiredDefaultDestinationName(), messageSelector, targetClass); + } + } + + @Override + public @Nullable T receiveSelectedAndConvert(Destination destination, @Nullable String messageSelector, + Class targetClass) throws MessagingException { + + Message message = doReceiveSelected(destination, messageSelector); + if (message != null) { + return doConvert(message, targetClass); + } + else { + return null; + } + } + + @Override + public @Nullable T receiveSelectedAndConvert(String destinationName, @Nullable String messageSelector, + Class targetClass) throws MessagingException { + + Message message = doReceiveSelected(destinationName, messageSelector); + if (message != null) { + return doConvert(message, targetClass); + } + else { + return null; + } + } + + @Override + public @Nullable Message sendAndReceive(Message requestMessage) throws MessagingException { Destination defaultDestination = getDefaultDestination(); if (defaultDestination != null) { return sendAndReceive(defaultDestination, requestMessage); @@ -277,7 +359,9 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } @Override - public @Nullable Message sendAndReceive(String destinationName, Message requestMessage) throws MessagingException { + public @Nullable Message sendAndReceive(String destinationName, Message requestMessage) + throws MessagingException { + return doSendAndReceive(destinationName, requestMessage); } @@ -289,7 +373,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } @Override - public @Nullable T convertSendAndReceive(Object request, Class targetClass) { + public @Nullable T convertSendAndReceive(Object request, Class targetClass) throws MessagingException { return convertSendAndReceive(request, targetClass, null); } @@ -301,7 +385,9 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } @Override - public @Nullable T convertSendAndReceive(Object request, Class targetClass, @Nullable MessagePostProcessor postProcessor) { + public @Nullable T convertSendAndReceive(Object request, Class targetClass, + @Nullable MessagePostProcessor postProcessor) throws MessagingException { + Destination defaultDestination = getDefaultDestination(); if (defaultDestination != null) { return convertSendAndReceive(defaultDestination, request, targetClass, postProcessor); @@ -320,14 +406,16 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate @SuppressWarnings("unchecked") @Override - public @Nullable T convertSendAndReceive(String destinationName, Object request, @Nullable Map headers, - Class targetClass, @Nullable MessagePostProcessor postProcessor) { + public @Nullable T convertSendAndReceive(String destinationName, Object request, + @Nullable Map headers, Class targetClass, + @Nullable MessagePostProcessor postProcessor) throws MessagingException { Message requestMessage = doConvert(request, headers, postProcessor); Message replyMessage = sendAndReceive(destinationName, requestMessage); return (replyMessage != null ? (T) getMessageConverter().fromMessage(replyMessage, targetClass) : null); } + @Override protected void doSend(Destination destination, Message message) { try { @@ -368,6 +456,26 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate } } + protected @Nullable Message doReceiveSelected(Destination destination, @Nullable String messageSelector) { + try { + jakarta.jms.Message jmsMessage = obtainJmsTemplate().receiveSelected(destination, messageSelector); + return convertJmsMessage(jmsMessage); + } + catch (JmsException ex) { + throw convertJmsException(ex); + } + } + + protected @Nullable Message doReceiveSelected(String destinationName, @Nullable String messageSelector) { + try { + jakarta.jms.Message jmsMessage = obtainJmsTemplate().receiveSelected(destinationName, messageSelector); + return convertJmsMessage(jmsMessage); + } + catch (JmsException ex) { + throw convertJmsException(ex); + } + } + @Override protected @Nullable Message doSendAndReceive(Destination destination, Message requestMessage) { try { diff --git a/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java b/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java index efa1c866dbf..ffffd3219ab 100644 --- a/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java +++ b/spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java @@ -35,6 +35,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.jms.JmsException; import org.springframework.jms.connection.ConnectionFactoryUtils; import org.springframework.jms.connection.JmsResourceHolder; +import org.springframework.jms.support.JmsAccessor; import org.springframework.jms.support.JmsUtils; import org.springframework.jms.support.QosSettings; import org.springframework.jms.support.converter.MessageConverter; @@ -47,10 +48,9 @@ import org.springframework.util.ClassUtils; /** * Helper class that simplifies synchronous JMS access code. * - *

If you want to use dynamic destination creation, you must specify - * the type of JMS destination to create, using the "pubSubDomain" property. - * For other operations, this is not necessary. Point-to-Point (Queues) is the default - * domain. + *

If you want to use dynamic destination creation, you must specify the type of + * JMS destination to create, using the "pubSubDomain" property. For other operations, + * this is not necessary. Point-to-Point (Queues) is the default domain. * *

Default settings for JMS Sessions are "not transacted" and "auto-acknowledge". * As defined by the Jakarta EE specification, the transaction and acknowledgement @@ -89,6 +89,8 @@ import org.springframework.util.ClassUtils; * @see #setMessageConverter * @see jakarta.jms.MessageProducer * @see jakarta.jms.MessageConsumer + * @see JmsMessagingTemplate + * @see JmsClient */ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations { @@ -101,8 +103,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations private @Nullable Object defaultDestination; - private @Nullable MessageConverter messageConverter; - + private MessageConverter messageConverter = new SimpleMessageConverter(); private boolean messageIdEnabled = true; @@ -114,7 +115,6 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations private long deliveryDelay = -1; - private boolean explicitQosEnabled = false; private int deliveryMode = Message.DEFAULT_DELIVERY_MODE; @@ -134,7 +134,6 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations * @see #setConnectionFactory */ public JmsTemplate() { - initDefaultStrategies(); } /** @@ -142,21 +141,37 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations * @param connectionFactory the ConnectionFactory to obtain Connections from */ public JmsTemplate(ConnectionFactory connectionFactory) { - this(); setConnectionFactory(connectionFactory); afterPropertiesSet(); } /** - * Initialize the default implementations for the template's strategies: - * DynamicDestinationResolver and SimpleMessageConverter. - * @see #setDestinationResolver - * @see #setMessageConverter - * @see org.springframework.jms.support.destination.DynamicDestinationResolver - * @see org.springframework.jms.support.converter.SimpleMessageConverter + * Copy constructor for a derived JmsTemplate. + * @param original the original template to copy from + * @since 7.0 */ - protected void initDefaultStrategies() { - setMessageConverter(new SimpleMessageConverter()); + public JmsTemplate(JmsAccessor original) { + setConnectionFactory(original.getConnectionFactory()); + setSessionTransacted(original.isSessionTransacted()); + setSessionAcknowledgeMode(original.getSessionAcknowledgeMode()); + if (original instanceof JmsDestinationAccessor destinationAccessor) { + setDestinationResolver(destinationAccessor.getDestinationResolver()); + setPubSubDomain(destinationAccessor.isPubSubDomain()); + } + if (original instanceof JmsTemplate originalTemplate) { + setDefaultDestination(originalTemplate.getDefaultDestination()); + setMessageConverter(originalTemplate.getMessageConverter()); + setMessageIdEnabled(originalTemplate.isMessageIdEnabled()); + setMessageTimestampEnabled(originalTemplate.isMessageTimestampEnabled()); + setPubSubNoLocal(originalTemplate.isPubSubNoLocal()); + setReceiveTimeout(originalTemplate.getReceiveTimeout()); + setDeliveryDelay(originalTemplate.getDeliveryDelay()); + setExplicitQosEnabled(originalTemplate.isExplicitQosEnabled()); + setDeliveryMode(originalTemplate.getDeliveryMode()); + setPriority(originalTemplate.getPriority()); + setTimeToLive(originalTemplate.getTimeToLive()); + setObservationRegistry(originalTemplate.getObservationRegistry()); + } } @@ -236,26 +251,18 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations * @see #receiveAndConvert * @see org.springframework.jms.support.converter.SimpleMessageConverter */ - public void setMessageConverter(@Nullable MessageConverter messageConverter) { + public void setMessageConverter(MessageConverter messageConverter) { + Assert.notNull(messageConverter, "MessageConverter must not be null"); this.messageConverter = messageConverter; } /** * Return the message converter for this template. */ - public @Nullable MessageConverter getMessageConverter() { + public MessageConverter getMessageConverter() { return this.messageConverter; } - private MessageConverter getRequiredMessageConverter() throws IllegalStateException { - MessageConverter converter = getMessageConverter(); - if (converter == null) { - throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate."); - } - return converter; - } - - /** * Set whether message IDs are enabled. Default is "true". *

This is only a hint to the JMS producer. @@ -345,7 +352,6 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations return this.deliveryDelay; } - /** * Set if the QOS values (deliveryMode, priority, timeToLive) * should be used for sending a message. @@ -447,7 +453,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations * Set the time-to-live of the message when sending. *

Since a default value may be defined administratively, * this is only used when "isExplicitQosEnabled" equals "true". - * @param timeToLive the message's lifetime (in milliseconds) + * @param timeToLive the message lifetime (in milliseconds) * @see #isExplicitQosEnabled * @see jakarta.jms.Message#DEFAULT_TIME_TO_LIVE * @see jakarta.jms.MessageProducer#send(jakarta.jms.Message, int, int, long) @@ -465,14 +471,23 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations /** * Configure the {@link ObservationRegistry} to use for recording JMS observations. - * @param observationRegistry the observation registry to use. + * @param observationRegistry the observation registry to use * @since 6.1 * @see io.micrometer.jakarta9.instrument.jms.JmsInstrumentation */ - public void setObservationRegistry(ObservationRegistry observationRegistry) { + public void setObservationRegistry(@Nullable ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; } + /** + * Return the {@link ObservationRegistry} to use for recording JMS observations. + * @since 7.0 + */ + public @Nullable ObservationRegistry getObservationRegistry() { + return this.observationRegistry; + } + + //--------------------------------------------------------------------------------------- // JmsOperations execute methods //--------------------------------------------------------------------------------------- @@ -667,12 +682,12 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations @Override public void convertAndSend(Destination destination, Object message) throws JmsException { - send(destination, session -> getRequiredMessageConverter().toMessage(message, session)); + send(destination, session -> getMessageConverter().toMessage(message, session)); } @Override public void convertAndSend(String destinationName, Object message) throws JmsException { - send(destinationName, session -> getRequiredMessageConverter().toMessage(message, session)); + send(destinationName, session -> getMessageConverter().toMessage(message, session)); } @Override @@ -687,23 +702,21 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations } @Override - public void convertAndSend( - Destination destination, Object message, MessagePostProcessor postProcessor) + public void convertAndSend(Destination destination, Object message, MessagePostProcessor postProcessor) throws JmsException { send(destination, session -> { - Message msg = getRequiredMessageConverter().toMessage(message, session); + Message msg = getMessageConverter().toMessage(message, session); return postProcessor.postProcessMessage(msg); }); } @Override - public void convertAndSend( - String destinationName, Object message, MessagePostProcessor postProcessor) - throws JmsException { + public void convertAndSend(String destinationName, Object message, MessagePostProcessor postProcessor) + throws JmsException { send(destinationName, session -> { - Message msg = getRequiredMessageConverter().toMessage(message, session); + Message msg = getMessageConverter().toMessage(message, session); return postProcessor.postProcessMessage(msg); }); } @@ -855,7 +868,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations protected @Nullable Object doConvertFromMessage(@Nullable Message message) { if (message != null) { try { - return getRequiredMessageConverter().fromMessage(message); + return getMessageConverter().fromMessage(message); } catch (JMSException ex) { throw convertJmsAccessException(ex); @@ -1178,6 +1191,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations } } + private abstract static class MicrometerInstrumentation { static Session instrumentSession(Session session, ObservationRegistry registry) { diff --git a/spring-jms/src/test/java/org/springframework/jms/core/JmsClientTests.java b/spring-jms/src/test/java/org/springframework/jms/core/JmsClientTests.java new file mode 100644 index 00000000000..687b9ba1a96 --- /dev/null +++ b/spring-jms/src/test/java/org/springframework/jms/core/JmsClientTests.java @@ -0,0 +1,513 @@ +/* + * Copyright 2002-present 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 + * + * https://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.core; + +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; + +import jakarta.jms.Connection; +import jakarta.jms.ConnectionFactory; +import jakarta.jms.DeliveryMode; +import jakarta.jms.Destination; +import jakarta.jms.JMSException; +import jakarta.jms.MessageConsumer; +import jakarta.jms.MessageProducer; +import jakarta.jms.Queue; +import jakarta.jms.Session; +import jakarta.jms.TextMessage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import org.springframework.jms.InvalidDestinationException; +import org.springframework.jms.MessageNotReadableException; +import org.springframework.jms.StubTextMessage; +import org.springframework.jms.support.destination.DestinationResolutionException; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.converter.GenericMessageConverter; +import org.springframework.messaging.support.MessageBuilder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link JmsClient}. + * + * @author Juergen Hoeller + * @since 7.0 + */ +@ExtendWith(MockitoExtension.class) +class JmsClientTests { + + @Captor + private ArgumentCaptor messageCreator; + + @Mock + private JmsTemplate jmsTemplate; + + private JmsClient jmsClient; + + + @BeforeEach + void setup() { + this.jmsClient = JmsClient.create(this.jmsTemplate); + } + + @Test + void send() { + Destination destination = new Destination() {}; + Message message = createTextMessage(); + + this.jmsClient.destination(destination).send(message); + verify(this.jmsTemplate).send(eq(destination), this.messageCreator.capture()); + assertTextMessage(this.messageCreator.getValue()); + } + + @Test + void sendName() { + Message message = createTextMessage(); + + this.jmsClient.destination("myQueue").send(message); + verify(this.jmsTemplate).send(eq("myQueue"), this.messageCreator.capture()); + assertTextMessage(this.messageCreator.getValue()); + } + + @Test + void convertAndSendPayload() throws JMSException { + Destination destination = new Destination() {}; + + this.jmsClient.destination(destination).send("my Payload"); + verify(this.jmsTemplate).send(eq(destination), this.messageCreator.capture()); + TextMessage textMessage = createTextMessage(this.messageCreator.getValue()); + assertThat(textMessage.getText()).isEqualTo("my Payload"); + } + + @Test + void convertAndSendPayloadName() throws JMSException { + this.jmsClient.destination("myQueue").send("my Payload"); + verify(this.jmsTemplate).send(eq("myQueue"), this.messageCreator.capture()); + TextMessage textMessage = createTextMessage(this.messageCreator.getValue()); + assertThat(textMessage.getText()).isEqualTo("my Payload"); + } + + @Test + void convertAndSendPayloadAndHeaders() { + Destination destination = new Destination() {}; + Map headers = new HashMap<>(); + headers.put("foo", "bar"); + + this.jmsClient.destination(destination).send("Hello", headers); + verify(this.jmsTemplate).send(eq(destination), this.messageCreator.capture()); + assertTextMessage(this.messageCreator.getValue()); // see createTextMessage + } + + @Test + void convertAndSendPayloadAndHeadersName() { + Map headers = new HashMap<>(); + headers.put("foo", "bar"); + + this.jmsClient.destination("myQueue").send("Hello", headers); + verify(this.jmsTemplate).send(eq("myQueue"), this.messageCreator.capture()); + assertTextMessage(this.messageCreator.getValue()); // see createTextMessage + } + + @Test + void receive() { + Destination destination = new Destination() {}; + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receive(destination)).willReturn(jmsMessage); + + Message message = this.jmsClient.destination(destination).receive().get(); + verify(this.jmsTemplate).receive(destination); + assertTextMessage(message); + } + + @Test + void receiveName() { + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + Message message = this.jmsClient.destination("myQueue").receive().get(); + verify(this.jmsTemplate).receive("myQueue"); + assertTextMessage(message); + } + + @Test + void receiveSelected() { + Destination destination = new Destination() {}; + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receiveSelected(destination, "selector")).willReturn(jmsMessage); + + Message message = this.jmsClient.destination(destination).receive("selector").get(); + verify(this.jmsTemplate).receiveSelected(destination, "selector"); + assertTextMessage(message); + } + + @Test + void receiveSelectedName() { + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + Message message = this.jmsClient.destination("myQueue").receive("selector").get(); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + assertTextMessage(message); + } + + @Test + void receiveAndConvert() { + Destination destination = new Destination() {}; + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receive(destination)).willReturn(jmsMessage); + + String payload = this.jmsClient.destination(destination).receive(String.class).get(); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receive(destination); + } + + @Test + void receiveAndConvertName() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + String payload = this.jmsClient.destination("myQueue").receive(String.class).get(); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receive("myQueue"); + } + + @Test + void receiveAndConvertWithConversion() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("123"); + given(this.jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + this.jmsClient = JmsClient.create(this.jmsTemplate, new GenericMessageConverter()); + + Integer payload = this.jmsClient.destination("myQueue").receive(Integer.class).get(); + assertThat(payload).isEqualTo(Integer.valueOf(123)); + verify(this.jmsTemplate).receive("myQueue"); + } + + @Test + void receiveAndConvertNoConverter() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("Hello"); + given(this.jmsTemplate.receive("myQueue")).willReturn(jmsMessage); + + assertThatExceptionOfType(org.springframework.messaging.converter.MessageConversionException.class).isThrownBy(() -> + this.jmsClient.destination("myQueue").receive(Writer.class)); + } + + @Test + void receiveAndConvertNoInput() { + given(this.jmsTemplate.receive("myQueue")).willReturn(null); + + assertThat(this.jmsClient.destination("myQueue").receive(String.class)).isEmpty(); + } + + @Test + void receiveSelectedAndConvert() { + Destination destination = new Destination() {}; + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receiveSelected(destination, "selector")).willReturn(jmsMessage); + + String payload = this.jmsClient.destination(destination).receive("selector", String.class).get(); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receiveSelected(destination, "selector"); + } + + @Test + void receiveSelectedAndConvertName() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + String payload = this.jmsClient.destination("myQueue").receive("selector", String.class).get(); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + } + + @Test + void receiveSelectedAndConvertWithConversion() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("123"); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + this.jmsClient = JmsClient.create(this.jmsTemplate, new GenericMessageConverter()); + + Integer payload = this.jmsClient.destination("myQueue").receive("selector", Integer.class).get(); + assertThat(payload).isEqualTo(Integer.valueOf(123)); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + } + + @Test + void receiveSelectedAndConvertNoConverter() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("Hello"); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + assertThatExceptionOfType(org.springframework.messaging.converter.MessageConversionException.class).isThrownBy(() -> + this.jmsClient.destination("myQueue").receive("selector", Writer.class)); + } + + @Test + void receiveSelectedAndConvertNoInput() { + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(null); + + assertThat(this.jmsClient.destination("myQueue").receive("selector", String.class)).isEmpty(); + } + + @Test + void sendAndReceive() { + Destination destination = new Destination() {}; + Message request = createTextMessage(); + jakarta.jms.Message replyJmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.sendAndReceive(eq(destination), any())).willReturn(replyJmsMessage); + + Message actual = this.jmsClient.destination(destination).sendAndReceive(request).get(); + verify(this.jmsTemplate, times(1)).sendAndReceive(eq(destination), any()); + assertTextMessage(actual); + } + + @Test + void sendAndReceiveName() { + Message request = createTextMessage(); + jakarta.jms.Message replyJmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.sendAndReceive(eq("myQueue"), any())).willReturn(replyJmsMessage); + + Message actual = this.jmsClient.destination("myQueue").sendAndReceive(request).get(); + verify(this.jmsTemplate, times(1)).sendAndReceive(eq("myQueue"), any()); + assertTextMessage(actual); + } + + @Test + void convertSendAndReceivePayload() { + Destination destination = new Destination() {}; + jakarta.jms.Message replyJmsMessage = createJmsTextMessage("My reply"); + given(this.jmsTemplate.sendAndReceive(eq(destination), any())).willReturn(replyJmsMessage); + + String reply = this.jmsClient.destination(destination).sendAndReceive("my Payload", String.class).get(); + verify(this.jmsTemplate, times(1)).sendAndReceive(eq(destination), any()); + assertThat(reply).isEqualTo("My reply"); + } + + @Test + void convertSendAndReceivePayloadName() { + jakarta.jms.Message replyJmsMessage = createJmsTextMessage("My reply"); + given(this.jmsTemplate.sendAndReceive(eq("myQueue"), any())).willReturn(replyJmsMessage); + + String reply = this.jmsClient.destination("myQueue").sendAndReceive("my Payload", String.class).get(); + verify(this.jmsTemplate, times(1)).sendAndReceive(eq("myQueue"), any()); + assertThat(reply).isEqualTo("My reply"); + } + + @Test + void convertMessageNotReadableException() { + willThrow(MessageNotReadableException.class).given(this.jmsTemplate).receive("myQueue"); + + assertThatExceptionOfType(MessagingException.class).isThrownBy(() -> + this.jmsClient.destination("myQueue").receive()); + } + + @Test + void convertDestinationResolutionExceptionOnSend() { + Destination destination = new Destination() {}; + willThrow(DestinationResolutionException.class).given(this.jmsTemplate).send(eq(destination), any()); + + assertThatExceptionOfType(org.springframework.messaging.core.DestinationResolutionException.class).isThrownBy(() -> + this.jmsClient.destination(destination).send(createTextMessage())); + } + + @Test + void convertDestinationResolutionExceptionOnReceive() { + Destination destination = new Destination() {}; + willThrow(DestinationResolutionException.class).given(this.jmsTemplate).receive(destination); + + assertThatExceptionOfType(org.springframework.messaging.core.DestinationResolutionException.class).isThrownBy(() -> + this.jmsClient.destination(destination).receive()); + } + + @Test + void convertInvalidDestinationExceptionOnSendAndReceiveWithName() { + willThrow(InvalidDestinationException.class).given(this.jmsTemplate).sendAndReceive(eq("unknownQueue"), any()); + + assertThatExceptionOfType(org.springframework.messaging.core.DestinationResolutionException.class).isThrownBy(() -> + this.jmsClient.destination("unknownQueue").sendAndReceive(createTextMessage())); + } + + @Test + void convertInvalidDestinationExceptionOnSendAndReceive() { + Destination destination = new Destination() {}; + willThrow(InvalidDestinationException.class).given(this.jmsTemplate).sendAndReceive(eq(destination), any()); + + assertThatExceptionOfType(org.springframework.messaging.core.DestinationResolutionException.class).isThrownBy(() -> + this.jmsClient.destination(destination).sendAndReceive(createTextMessage())); + } + + @Test + void sendWithDefaults() throws Exception { + ConnectionFactory connectionFactory = mock(); + Connection connection = mock(); + Session session = mock(); + Queue queue = mock(); + MessageProducer messageProducer = mock(); + TextMessage textMessage = mock(); + + given(connectionFactory.createConnection()).willReturn(connection); + given(connection.createSession(false, Session.AUTO_ACKNOWLEDGE)).willReturn(session); + given(session.createProducer(queue)).willReturn(messageProducer); + given(session.createTextMessage("just testing")).willReturn(textMessage); + + JmsClient.create(connectionFactory).destination(queue) + .send("just testing"); + + verify(messageProducer).send(textMessage); + verify(messageProducer).close(); + verify(session).close(); + verify(connection).close(); + } + + @Test + void sendWithCustomSettings() throws Exception { + ConnectionFactory connectionFactory = mock(); + Connection connection = mock(); + Session session = mock(); + Queue queue = mock(); + MessageProducer messageProducer = mock(); + TextMessage textMessage = mock(); + + given(connectionFactory.createConnection()).willReturn(connection); + given(connection.createSession(false, Session.AUTO_ACKNOWLEDGE)).willReturn(session); + given(session.createProducer(queue)).willReturn(messageProducer); + given(session.createTextMessage("just testing")).willReturn(textMessage); + + JmsClient.create(connectionFactory).destination(queue) + .withDeliveryDelay(0).withDeliveryPersistent(false).withPriority(2).withTimeToLive(3) + .send("just testing"); + + verify(messageProducer).setDeliveryDelay(0); + verify(messageProducer).send(textMessage, DeliveryMode.NON_PERSISTENT, 2, 3); + verify(messageProducer).close(); + verify(session).close(); + verify(connection).close(); + } + + @Test + void receiveWithDefaults() throws Exception { + ConnectionFactory connectionFactory = mock(); + Connection connection = mock(); + Session session = mock(); + Queue queue = mock(); + MessageConsumer messageConsumer = mock(); + TextMessage textMessage = mock(); + + given(connectionFactory.createConnection()).willReturn(connection); + given(connection.createSession(false, Session.AUTO_ACKNOWLEDGE)).willReturn(session); + given(session.createConsumer(queue, null)).willReturn(messageConsumer); + given(messageConsumer.receive()).willReturn(textMessage); + given(textMessage.getText()).willReturn("Hello World!"); + + String result = JmsClient.create(connectionFactory).destination(queue) + .receive(String.class).get(); + assertThat(result).isEqualTo("Hello World!"); + + verify(connection).start(); + verify(messageConsumer).close(); + verify(session).close(); + verify(connection).close(); + } + + @Test + void receiveWithCustomTimeout() throws Exception { + ConnectionFactory connectionFactory = mock(); + Connection connection = mock(); + Session session = mock(); + Queue queue = mock(); + MessageConsumer messageConsumer = mock(); + TextMessage textMessage = mock(); + + given(connectionFactory.createConnection()).willReturn(connection); + given(connection.createSession(false, Session.AUTO_ACKNOWLEDGE)).willReturn(session); + given(session.createConsumer(queue, null)).willReturn(messageConsumer); + given(messageConsumer.receive(10)).willReturn(textMessage); + given(textMessage.getText()).willReturn("Hello World!"); + + String result = JmsClient.create(connectionFactory).destination(queue) + .withReceiveTimeout(10).receive(String.class).get(); + assertThat(result).isEqualTo("Hello World!"); + + verify(connection).start(); + verify(messageConsumer).close(); + verify(session).close(); + verify(connection).close(); + } + + + private Message createTextMessage(String payload) { + return MessageBuilder.withPayload(payload).setHeader("foo", "bar").build(); + } + + private Message createTextMessage() { + return createTextMessage("Hello"); + } + + private jakarta.jms.Message createJmsTextMessage(String payload) { + StubTextMessage jmsMessage = new StubTextMessage(payload); + jmsMessage.setStringProperty("foo", "bar"); + return jmsMessage; + } + + private jakarta.jms.Message createJmsTextMessage() { + return createJmsTextMessage("Hello"); + } + + private void assertTextMessage(MessageCreator messageCreator) { + try { + TextMessage jmsMessage = createTextMessage(messageCreator); + assertThat(jmsMessage.getText()).as("Wrong body message").isEqualTo("Hello"); + assertThat(jmsMessage.getStringProperty("foo")).as("Invalid foo property").isEqualTo("bar"); + } + catch (JMSException e) { + throw new IllegalStateException("Wrong text message", e); + } + } + + private void assertTextMessage(Message message) { + assertThat(message).as("message should not be null").isNotNull(); + assertThat(message.getPayload()).as("Wrong payload").isEqualTo("Hello"); + assertThat(message.getHeaders().get("foo")).as("Invalid foo property").isEqualTo("bar"); + } + + protected TextMessage createTextMessage(MessageCreator creator) throws JMSException { + Session mock = mock(); + given(mock.createTextMessage(any())).willAnswer( + (Answer) invocation -> + new StubTextMessage((String) invocation.getArguments()[0])); + jakarta.jms.Message message = creator.createMessage(mock); + verify(mock).createTextMessage(any()); + return (TextMessage) message; + } + +} diff --git a/spring-jms/src/test/java/org/springframework/jms/core/JmsMessagingTemplateTests.java b/spring-jms/src/test/java/org/springframework/jms/core/JmsMessagingTemplateTests.java index 1d01190d4b4..3a650eda9a8 100644 --- a/spring-jms/src/test/java/org/springframework/jms/core/JmsMessagingTemplateTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/core/JmsMessagingTemplateTests.java @@ -64,6 +64,7 @@ import static org.mockito.Mockito.verify; * Tests for {@link JmsMessagingTemplate}. * * @author Stephane Nicoll + * @author Juergen Hoeller */ @ExtendWith(MockitoExtension.class) class JmsMessagingTemplateTests { @@ -110,15 +111,13 @@ class JmsMessagingTemplateTests { void customConverterAlwaysTakesPrecedence() { MessageConverter customMessageConverter = mock(); JmsMessagingTemplate messagingTemplate = new JmsMessagingTemplate(); - messagingTemplate.setJmsMessageConverter( - new MessagingMessageConverter(customMessageConverter)); + messagingTemplate.setJmsMessageConverter(new MessagingMessageConverter(customMessageConverter)); messagingTemplate.setJmsTemplate(this.jmsTemplate); messagingTemplate.afterPropertiesSet(); assertPayloadConverter(messagingTemplate, customMessageConverter); } - private void assertPayloadConverter(JmsMessagingTemplate messagingTemplate, - MessageConverter messageConverter) { + private void assertPayloadConverter(JmsMessagingTemplate messagingTemplate, MessageConverter messageConverter) { MessageConverter jmsMessageConverter = messagingTemplate.getJmsMessageConverter(); assertThat(jmsMessageConverter).isNotNull(); assertThat(jmsMessageConverter.getClass()).isEqualTo(MessagingMessageConverter.class); @@ -170,19 +169,21 @@ class JmsMessagingTemplateTests { void sendNoDefaultSet() { Message message = createTextMessage(); - assertThatIllegalStateException().isThrownBy(() -> - this.messagingTemplate.send(message)); + assertThatIllegalStateException().isThrownBy(() -> this.messagingTemplate.send(message)); } @Test void sendPropertyInjection() { - JmsMessagingTemplate t = new JmsMessagingTemplate(); - t.setJmsTemplate(this.jmsTemplate); - t.setDefaultDestinationName("myQueue"); - t.afterPropertiesSet(); + MessageConverter messageConverter = new SimpleMessageConverter(); + given(this.jmsTemplate.getMessageConverter()).willReturn(messageConverter); + + this.messagingTemplate = new JmsMessagingTemplate(); + this.messagingTemplate.setJmsTemplate(this.jmsTemplate); + this.messagingTemplate.setDefaultDestinationName("myQueue"); + this.messagingTemplate.afterPropertiesSet(); Message message = createTextMessage(); - t.send(message); + this.messagingTemplate.send(message); verify(this.jmsTemplate).send(eq("myQueue"), this.messageCreator.capture()); assertTextMessage(this.messageCreator.getValue()); } @@ -236,8 +237,7 @@ class JmsMessagingTemplateTests { void convertAndSendCustomJmsMessageConverter() { this.messagingTemplate.setJmsMessageConverter(new SimpleMessageConverter() { @Override - public jakarta.jms.Message toMessage(Object object, Session session) - throws org.springframework.jms.support.converter.MessageConversionException { + public jakarta.jms.Message toMessage(Object object, Session session) { throw new org.springframework.jms.support.converter.MessageConversionException("Test exception"); } }); @@ -245,9 +245,9 @@ class JmsMessagingTemplateTests { this.messagingTemplate.convertAndSend("myQueue", "msg to convert"); verify(this.jmsTemplate).send(eq("myQueue"), this.messageCreator.capture()); - assertThatExceptionOfType(org.springframework.messaging.converter.MessageConversionException.class).isThrownBy(() -> - this.messageCreator.getValue().createMessage(mock())) - .withMessageContaining("Test exception"); + assertThatExceptionOfType(org.springframework.messaging.converter.MessageConversionException.class) + .isThrownBy(() -> this.messageCreator.getValue().createMessage(mock())) + .withMessageContaining("Test exception"); } @Test @@ -317,8 +317,56 @@ class JmsMessagingTemplateTests { @Test void receiveNoDefaultSet() { - assertThatIllegalStateException().isThrownBy( - this.messagingTemplate::receive); + assertThatIllegalStateException().isThrownBy(this.messagingTemplate::receive); + } + + @Test + void receiveSelected() { + Destination destination = new Destination() {}; + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receiveSelected(destination, "selector")).willReturn(jmsMessage); + + Message message = this.messagingTemplate.receiveSelected(destination, "selector"); + verify(this.jmsTemplate).receiveSelected(destination, "selector"); + assertTextMessage(message); + } + + @Test + void receiveSelectedName() { + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + Message message = this.messagingTemplate.receiveSelected("myQueue", "selector"); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + assertTextMessage(message); + } + + @Test + void receiveSelectedDefaultDestination() { + Destination destination = new Destination() {}; + this.messagingTemplate.setDefaultDestination(destination); + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receiveSelected(destination, "selector")).willReturn(jmsMessage); + + Message message = this.messagingTemplate.receiveSelected("selector"); + verify(this.jmsTemplate).receiveSelected(destination, "selector"); + assertTextMessage(message); + } + + @Test + void receiveSelectedDefaultDestinationName() { + this.messagingTemplate.setDefaultDestinationName("myQueue"); + jakarta.jms.Message jmsMessage = createJmsTextMessage(); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + Message message = this.messagingTemplate.receiveSelected("selector"); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + assertTextMessage(message); + } + + @Test + void receiveSelectedNoDefaultSet() { + assertThatIllegalStateException().isThrownBy(() -> this.messagingTemplate.receiveSelected("selector")); } @Test @@ -393,6 +441,78 @@ class JmsMessagingTemplateTests { assertThat(this.messagingTemplate.receiveAndConvert("myQueue", String.class)).isNull(); } + @Test + void receiveSelectedAndConvert() { + Destination destination = new Destination() {}; + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receiveSelected(destination, "selector")).willReturn(jmsMessage); + + String payload = this.messagingTemplate.receiveSelectedAndConvert(destination, "selector", String.class); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receiveSelected(destination, "selector"); + } + + @Test + void receiveSelectedAndConvertName() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + String payload = this.messagingTemplate.receiveSelectedAndConvert("myQueue", "selector", String.class); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + } + + @Test + void receiveSelectedAndConvertDefaultDestination() { + Destination destination = new Destination() {}; + this.messagingTemplate.setDefaultDestination(destination); + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receiveSelected(destination, "selector")).willReturn(jmsMessage); + + String payload = this.messagingTemplate.receiveSelectedAndConvert("selector", String.class); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receiveSelected(destination, "selector"); + } + + @Test + void receiveSelectedAndConvertDefaultDestinationName() { + this.messagingTemplate.setDefaultDestinationName("myQueue"); + jakarta.jms.Message jmsMessage = createJmsTextMessage("my Payload"); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + String payload = this.messagingTemplate.receiveSelectedAndConvert("selector", String.class); + assertThat(payload).isEqualTo("my Payload"); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + } + + @Test + void receiveSelectedAndConvertWithConversion() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("123"); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + this.messagingTemplate.setMessageConverter(new GenericMessageConverter()); + + Integer payload = this.messagingTemplate.receiveSelectedAndConvert("myQueue", "selector", Integer.class); + assertThat(payload).isEqualTo(Integer.valueOf(123)); + verify(this.jmsTemplate).receiveSelected("myQueue", "selector"); + } + + @Test + void receiveSelectedAndConvertNoConverter() { + jakarta.jms.Message jmsMessage = createJmsTextMessage("Hello"); + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(jmsMessage); + + assertThatExceptionOfType(org.springframework.messaging.converter.MessageConversionException.class).isThrownBy(() -> + this.messagingTemplate.receiveSelectedAndConvert("myQueue", "selector", Writer.class)); + } + + @Test + void receiveSelectedAndConvertNoInput() { + given(this.jmsTemplate.receiveSelected("myQueue", "selector")).willReturn(null); + + assertThat(this.messagingTemplate.receiveSelectedAndConvert("myQueue", "selector", String.class)).isNull(); + } + @Test void sendAndReceive() { Destination destination = new Destination() {}; @@ -444,9 +564,7 @@ class JmsMessagingTemplateTests { @Test void sendAndReceiveNoDefaultSet() { Message message = createTextMessage(); - - assertThatIllegalStateException().isThrownBy(() -> - this.messagingTemplate.sendAndReceive(message)); + assertThatIllegalStateException().isThrownBy(() -> this.messagingTemplate.sendAndReceive(message)); } @Test @@ -592,6 +710,7 @@ class JmsMessagingTemplateTests { this.messagingTemplate.sendAndReceive(destination, createTextMessage())); } + private void invokeMessageCreator() { willAnswer(invocation -> { MessageCreator messageCreator = (MessageCreator) invocation.getArguments()[1]; @@ -600,10 +719,8 @@ class JmsMessagingTemplateTests { }).given(this.jmsTemplate).send(eq("myQueue"), any()); } - private Message createTextMessage(String payload) { - return MessageBuilder - .withPayload(payload).setHeader("foo", "bar").build(); + return MessageBuilder.withPayload(payload).setHeader("foo", "bar").build(); } private Message createTextMessage() { @@ -620,7 +737,6 @@ class JmsMessagingTemplateTests { return createJmsTextMessage("Hello"); } - private void assertTextMessage(MessageCreator messageCreator) { try { TextMessage jmsMessage = createTextMessage(messageCreator); @@ -638,7 +754,6 @@ class JmsMessagingTemplateTests { assertThat(message.getHeaders().get("foo")).as("Invalid foo property").isEqualTo("bar"); } - protected TextMessage createTextMessage(MessageCreator creator) throws JMSException { Session mock = mock(); given(mock.createTextMessage(any())).willAnswer( diff --git a/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java b/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java index 89103b88638..d5d67ceb768 100644 --- a/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java @@ -103,14 +103,14 @@ class JmsTemplateTests { private JmsTemplate createTemplate() { JmsTemplate template = new JmsTemplate(); - JndiDestinationResolver destMan = new JndiDestinationResolver(); - destMan.setJndiTemplate(new JndiTemplate() { + JndiDestinationResolver resolver = new JndiDestinationResolver(); + resolver.setJndiTemplate(new JndiTemplate() { @Override protected Context createInitialContext() { return JmsTemplateTests.this.jndiContext; } }); - template.setDestinationResolver(destMan); + template.setDestinationResolver(resolver); template.setSessionTransacted(useTransactedTemplate()); return template; } 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 6e612e232bb..544157bb8d6 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 @@ -37,6 +37,7 @@ import org.springframework.util.Assert; * @author Mark Fisher * @author Rossen Stoyanchev * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.0 * @param the destination type */ @@ -112,12 +113,17 @@ public abstract class AbstractMessageSendingTemplate implements MessageSendin @Override public void convertAndSend(Object payload) throws MessagingException { - convertAndSend(payload, null); + convertAndSend(payload, null, null); } @Override public void convertAndSend(D destination, Object payload) throws MessagingException { - convertAndSend(destination, payload, (Map) null); + convertAndSend(destination, payload, null, null); + } + + @Override + public void convertAndSend(Object payload, Map headers) throws MessagingException { + convertAndSend(payload, headers, null); } @Override @@ -131,7 +137,7 @@ public abstract class AbstractMessageSendingTemplate implements MessageSendin public void convertAndSend(Object payload, @Nullable MessagePostProcessor postProcessor) throws MessagingException { - convertAndSend(getRequiredDefaultDestination(), payload, postProcessor); + convertAndSend(payload, null, postProcessor); } @Override @@ -141,6 +147,13 @@ public abstract class AbstractMessageSendingTemplate implements MessageSendin convertAndSend(destination, payload, null, postProcessor); } + @Override + public void convertAndSend(Object payload, @Nullable Map headers, + @Nullable MessagePostProcessor postProcessor) throws MessagingException { + + convertAndSend(getRequiredDefaultDestination(), payload, null, postProcessor); + } + @Override public void convertAndSend(D destination, Object payload, @Nullable Map headers, @Nullable MessagePostProcessor postProcessor) throws MessagingException { @@ -152,10 +165,10 @@ public abstract class AbstractMessageSendingTemplate implements MessageSendin /** * 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. + * headers and apply the given post-processor. * @param payload the Object to use as payload * @param headers the headers for the message to send - * @param postProcessor the post processor to apply to the message + * @param postProcessor the post-processor to apply to the message * @return the converted message */ protected Message doConvert(Object payload, @Nullable Map headers, 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 5ef212d4c17..1eec77c623f 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 @@ -21,6 +21,7 @@ import java.util.Map; import org.jspecify.annotations.Nullable; import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; /** * An extension of {@link AbstractMessageReceivingTemplate} that adds support for @@ -29,6 +30,7 @@ import org.springframework.messaging.Message; * @author Mark Fisher * @author Rossen Stoyanchev * @author Stephane Nicoll + * @author Juergen Hoeller * @since 4.0 * @param the destination type */ @@ -36,57 +38,72 @@ public abstract class AbstractMessagingTemplate extends AbstractMessageReceiv implements MessageRequestReplyOperations { @Override - public @Nullable Message sendAndReceive(Message requestMessage) { + public @Nullable Message sendAndReceive(Message requestMessage) throws MessagingException { return sendAndReceive(getRequiredDefaultDestination(), requestMessage); } @Override - public @Nullable Message sendAndReceive(D destination, Message requestMessage) { + public @Nullable Message sendAndReceive(D destination, Message requestMessage) throws MessagingException { return doSendAndReceive(destination, requestMessage); } - protected abstract @Nullable Message doSendAndReceive(D destination, Message requestMessage); - + @Override + public @Nullable T convertSendAndReceive(Object request, Class targetClass) throws MessagingException { + return convertSendAndReceive(request, null, targetClass, null); + } @Override - public @Nullable T convertSendAndReceive(Object request, Class targetClass) { - return convertSendAndReceive(getRequiredDefaultDestination(), request, targetClass); + public @Nullable T convertSendAndReceive(D destination, Object request, Class targetClass) throws MessagingException { + return convertSendAndReceive(destination, request, null, targetClass, null); } @Override - public @Nullable T convertSendAndReceive(D destination, Object request, Class targetClass) { - return convertSendAndReceive(destination, request, null, targetClass); + public @Nullable T convertSendAndReceive(Object request, @Nullable Map headers, Class targetClass) throws MessagingException { + return convertSendAndReceive(request, headers, targetClass, null); } @Override public @Nullable T convertSendAndReceive( - D destination, Object request, @Nullable Map headers, Class targetClass) { + D destination, Object request, @Nullable Map headers, Class targetClass) + throws MessagingException { return convertSendAndReceive(destination, request, headers, targetClass, null); } @Override public @Nullable T convertSendAndReceive( - Object request, Class targetClass, @Nullable MessagePostProcessor postProcessor) { + Object request, Class targetClass, @Nullable MessagePostProcessor postProcessor) + throws MessagingException { - return convertSendAndReceive(getRequiredDefaultDestination(), request, targetClass, postProcessor); + return convertSendAndReceive(request, null, targetClass, postProcessor); } @Override public @Nullable T convertSendAndReceive(D destination, Object request, Class targetClass, - @Nullable MessagePostProcessor postProcessor) { + @Nullable MessagePostProcessor postProcessor) throws MessagingException { return convertSendAndReceive(destination, request, null, targetClass, postProcessor); } + @SuppressWarnings("unchecked") + @Override + public @Nullable T convertSendAndReceive(Object request, @Nullable Map headers, + Class targetClass, @Nullable MessagePostProcessor postProcessor) throws MessagingException { + + return convertSendAndReceive(getRequiredDefaultDestination(), request, headers, targetClass, postProcessor); + } + @SuppressWarnings("unchecked") @Override public @Nullable T convertSendAndReceive(D destination, Object request, @Nullable Map headers, - Class targetClass, @Nullable MessagePostProcessor postProcessor) { + Class targetClass, @Nullable MessagePostProcessor postProcessor) throws MessagingException { Message requestMessage = doConvert(request, headers, postProcessor); - Message replyMessage = sendAndReceive(destination, requestMessage); + Message replyMessage = doSendAndReceive(destination, requestMessage); return (replyMessage != null ? (T) getMessageConverter().fromMessage(replyMessage, targetClass) : null); } + + protected abstract @Nullable Message doSendAndReceive(D destination, Message requestMessage); + } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/MessageRequestReplyOperations.java b/spring-messaging/src/main/java/org/springframework/messaging/core/MessageRequestReplyOperations.java index 49cef20cfb7..3fec2cce1a3 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/core/MessageRequestReplyOperations.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/core/MessageRequestReplyOperations.java @@ -28,6 +28,7 @@ import org.springframework.messaging.MessagingException; * * @author Mark Fisher * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.0 * @param the type of destination * @see GenericMessagingTemplate @@ -76,6 +77,21 @@ public interface MessageRequestReplyOperations { */ @Nullable T convertSendAndReceive(D destination, Object request, Class targetClass) throws MessagingException; + /** + * Convert the given request Object to serialized form, possibly using a + * {@link org.springframework.messaging.converter.MessageConverter}, send + * it as a {@link Message} with the given headers, to the specified destination, + * receive the reply and convert its body of the specified target class. + * @param request payload for the request message to send + * @param headers the headers for the request message to send + * @param targetClass the target type to convert the payload of the reply to + * @return the payload of the reply message, possibly {@code null} if the message + * could not be received, for example due to a timeout + * @since 7.0 + */ + @Nullable T convertSendAndReceive(Object request, @Nullable Map headers, Class targetClass) + throws MessagingException; + /** * Convert the given request Object to serialized form, possibly using a * {@link org.springframework.messaging.converter.MessageConverter}, send @@ -95,7 +111,7 @@ public interface MessageRequestReplyOperations { /** * Convert the given request Object to serialized form, possibly using a * {@link org.springframework.messaging.converter.MessageConverter}, - * apply the given post processor and send the resulting {@link Message} to a + * apply the given post-processor and send the resulting {@link Message} to a * default destination, receive the reply and convert its body of the given * target class. * @param request payload for the request message to send @@ -111,7 +127,7 @@ public interface MessageRequestReplyOperations { /** * Convert the given request Object to serialized form, possibly using a * {@link org.springframework.messaging.converter.MessageConverter}, - * apply the given post processor and send the resulting {@link Message} to the + * apply the given post-processor and send the resulting {@link Message} to the * given destination, receive the reply and convert its body of the given * target class. * @param destination the target destination @@ -127,7 +143,24 @@ public interface MessageRequestReplyOperations { /** * Convert the given request 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 + * wrap it as a message with the given headers, apply the given post-processor + * and send the resulting {@link Message} to the specified destination, receive + * the reply and convert its body of the given target class. + * @param request payload for the request message to send + * @param targetClass the target type to convert the payload of the reply to + * @param requestPostProcessor post process to apply to the request message + * @return the payload of the reply message, possibly {@code null} if the message + * could not be received, for example due to a timeout + * @since 7.0 + */ + @Nullable T convertSendAndReceive( + Object request, @Nullable Map headers, Class targetClass, + @Nullable MessagePostProcessor requestPostProcessor) throws MessagingException; + + /** + * Convert the given request 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 {@link Message} to the specified destination, receive * the reply and convert its body of the given target class. * @param destination the target destination 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 3a62f9d34a6..65ce8e47ab9 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 @@ -28,6 +28,7 @@ import org.springframework.messaging.MessagingException; * * @author Mark Fisher * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.0 * @param the destination type */ @@ -63,6 +64,16 @@ public interface MessageSendingOperations { */ void convertAndSend(D destination, 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 and send it to a default destination. + * @param payload the Object to use as payload + * @param headers the headers for the message to send + * @since 7.0 + */ + void convertAndSend(Object payload, Map headers) throws MessagingException; + /** * Convert the given Object to serialized form, possibly using a * {@link org.springframework.messaging.converter.MessageConverter}, @@ -77,33 +88,46 @@ public interface MessageSendingOperations { /** * 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 + * wrap it as a message, apply the given post-processor, and send * the resulting message to a default destination. * @param payload the Object to use as payload - * @param postProcessor the post processor to apply to the message + * @param postProcessor the post-processor to apply to the message */ void convertAndSend(Object payload, @Nullable 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, apply the given post processor, and send + * wrap it as a message, apply the given post-processor, and send * the resulting message to the given destination. * @param destination the target destination * @param payload the Object to use as payload - * @param postProcessor the post processor to apply to the message + * @param postProcessor the post-processor to apply to the message + */ + void convertAndSend(D destination, Object payload, @Nullable 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 payload the Object to use as payload + * @param headers the headers for the message to send + * @param postProcessor the post-processor to apply to the message + * @since 7.0 */ - void convertAndSend(D destination, Object payload, MessagePostProcessor postProcessor) throws MessagingException; + void convertAndSend(Object payload, @Nullable Map headers, + @Nullable 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, + * wrap it as a message with the given headers, apply the given post-processor, * and send the resulting message to the given destination. * @param destination the target destination * @param payload the Object to use as payload * @param headers the headers for the message to send - * @param postProcessor the post processor to apply to the message + * @param postProcessor the post-processor to apply to the message */ void convertAndSend(D destination, Object payload, @Nullable Map headers, @Nullable MessagePostProcessor postProcessor) throws MessagingException;