Browse Source

Introduce JmsClient with configurable settings per operation

Closes gh-32501
Closes gh-26840
pull/35163/head
Juergen Hoeller 6 months ago
parent
commit
6dc3c11828
  1. 195
      spring-jms/src/main/java/org/springframework/jms/core/DefaultJmsClient.java
  2. 248
      spring-jms/src/main/java/org/springframework/jms/core/JmsClient.java
  3. 117
      spring-jms/src/main/java/org/springframework/jms/core/JmsMessageOperations.java
  4. 166
      spring-jms/src/main/java/org/springframework/jms/core/JmsMessagingTemplate.java
  5. 98
      spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java
  6. 513
      spring-jms/src/test/java/org/springframework/jms/core/JmsClientTests.java
  7. 167
      spring-jms/src/test/java/org/springframework/jms/core/JmsMessagingTemplateTests.java
  8. 6
      spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java
  9. 23
      spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageSendingTemplate.java
  10. 45
      spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessagingTemplate.java
  11. 39
      spring-messaging/src/main/java/org/springframework/messaging/core/MessageRequestReplyOperations.java
  12. 38
      spring-messaging/src/main/java/org/springframework/messaging/core/MessageSendingOperations.java

195
spring-jms/src/main/java/org/springframework/jms/core/DefaultJmsClient.java

@ -0,0 +1,195 @@ @@ -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<String, Object> headers) throws MessagingException {
this.delegate.convertAndSend(payload, headers);
}
@Override
public Optional<Message<?>> receive() throws MessagingException {
return Optional.ofNullable(this.delegate.receive());
}
@Override
public <T> Optional<T> receive(Class<T> targetClass) throws MessagingException {
return Optional.ofNullable(this.delegate.receiveAndConvert(targetClass));
}
@Override
public Optional<Message<?>> receive(String messageSelector) throws MessagingException {
return Optional.ofNullable(this.delegate.receiveSelected(messageSelector));
}
@Override
public <T> Optional<T> receive(String messageSelector, Class<T> targetClass) throws MessagingException {
return Optional.ofNullable(this.delegate.receiveSelectedAndConvert(messageSelector, targetClass));
}
@Override
public Optional<Message<?>> sendAndReceive(Message<?> requestMessage) throws MessagingException {
return Optional.ofNullable(this.delegate.sendAndReceive(requestMessage));
}
@Override
public <T> Optional<T> sendAndReceive(Object request, Class<T> targetClass) throws MessagingException {
return Optional.ofNullable(this.delegate.convertSendAndReceive(request, targetClass));
}
@Override
public <T> Optional<T> sendAndReceive(Object request, Map<String, Object> headers, Class<T> targetClass)
throws MessagingException {
return Optional.ofNullable(this.delegate.convertSendAndReceive(request, headers, targetClass));
}
}
}

248
spring-jms/src/main/java/org/springframework/jms/core/JmsClient.java

@ -0,0 +1,248 @@ @@ -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.
*
* <p>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.
*
* <p>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<String, Object> 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<Message<?>> 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
*/
<T> Optional<T> receive(Class<T> 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<Message<?>> 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
*/
<T> Optional<T> receive(String messageSelector, Class<T> 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<Message<?>> 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
*/
<T> Optional<T> sendAndReceive(Object request, Class<T> 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
*/
<T> Optional<T> sendAndReceive(Object request, Map<String, Object> headers, Class<T> targetClass)
throws MessagingException;
}
}

117
spring-jms/src/main/java/org/springframework/jms/core/JmsMessageOperations.java

@ -30,10 +30,15 @@ import org.springframework.messaging.core.MessageSendingOperations; @@ -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}.
*
* <p>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<Destinati @@ -68,30 +73,30 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati
* @param payload the Object to use as payload
* @param headers the headers for the message to send
*/
void convertAndSend(String destinationName, Object payload, Map<String, Object> headers)
void convertAndSend(String destinationName, Object payload, @Nullable Map<String, Object> 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<String, Object> headers,
@Nullable MessagePostProcessor postProcessor) throws MessagingException;
@ -114,6 +119,81 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati @@ -114,6 +119,81 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati
*/
<T> @Nullable T receiveAndConvert(String destinationName, Class<T> 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
*/
<T> @Nullable T receiveSelectedAndConvert(@Nullable String messageSelector, Class<T> 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
*/
<T> @Nullable T receiveSelectedAndConvert(Destination destination, @Nullable String messageSelector,
Class<T> 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
*/
<T> @Nullable T receiveSelectedAndConvert(String destinationName, @Nullable String messageSelector,
Class<T> 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<Destinati @@ -121,7 +201,8 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati
* @return the reply, possibly {@code null} if the message could not be received,
* for example due to a timeout
*/
@Nullable Message<?> 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<Destinati @@ -134,7 +215,8 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati
* @return the payload of the reply message, possibly {@code null} if the message
* could not be received, for example due to a timeout
*/
<T> @Nullable T convertSendAndReceive(String destinationName, Object request, Class<T> targetClass) throws MessagingException;
<T> @Nullable T convertSendAndReceive(String destinationName, Object request, Class<T> targetClass)
throws MessagingException;
/**
* Convert the given request Object to serialized form, possibly using a
@ -148,13 +230,13 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati @@ -148,13 +230,13 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati
* @return the payload of the reply message, possibly {@code null} if the message
* could not be received, for example due to a timeout
*/
<T> @Nullable T convertSendAndReceive(String destinationName, Object request, @Nullable Map<String, Object> headers, Class<T> targetClass)
throws MessagingException;
<T> @Nullable T convertSendAndReceive(String destinationName, Object request,
@Nullable Map<String, Object> headers, Class<T> 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<Destinati @@ -165,12 +247,12 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati
* could not be received, for example due to a timeout
*/
<T> @Nullable T convertSendAndReceive(String destinationName, Object request, Class<T> 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<Destinati @@ -180,7 +262,8 @@ public interface JmsMessageOperations extends MessageSendingOperations<Destinati
* @return the payload of the reply message, possibly {@code null} if the message
* could not be received, for example due to a timeout
*/
<T> @Nullable T convertSendAndReceive(String destinationName, Object request, Map<String, Object> headers,
Class<T> targetClass, MessagePostProcessor requestPostProcessor) throws MessagingException;
<T> @Nullable T convertSendAndReceive(String destinationName, Object request,
@Nullable Map<String, Object> headers, Class<T> targetClass,
@Nullable MessagePostProcessor requestPostProcessor) throws MessagingException;
}

166
spring-jms/src/main/java/org/springframework/jms/core/JmsMessagingTemplate.java

@ -27,6 +27,7 @@ import org.jspecify.annotations.Nullable; @@ -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; @@ -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}.
*
* <p>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<Destination>
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<Destination> @@ -74,21 +83,29 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
/**
* 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<Destination> @@ -100,7 +117,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
* @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<Destination> @@ -114,14 +131,14 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
* 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}.
* <p>By default, a {@link MessagingMessageConverter} is defined using a
* {@link SimpleMessageConverter} to convert the payload of the message.
* <p>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<Destination> @@ -135,7 +152,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
/**
* 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<Destination> @@ -161,20 +178,19 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
@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<Destination> @@ -185,13 +201,15 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
@Override
public void convertAndSend(Object payload, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
public void convertAndSend(Object payload, @Nullable Map<String, Object> 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<Destination> @@ -228,7 +246,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
@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<Destination> @@ -239,7 +257,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
@Override
public <T> @Nullable T receiveAndConvert(Class<T> targetClass) {
public <T> @Nullable T receiveAndConvert(Class<T> targetClass) throws MessagingException {
Destination defaultDestination = getDefaultDestination();
if (defaultDestination != null) {
return receiveAndConvert(defaultDestination, targetClass);
@ -266,7 +284,71 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination> @@ -266,7 +284,71 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
@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 <T> @Nullable T receiveSelectedAndConvert(@Nullable String messageSelector, Class<T> targetClass)
throws MessagingException {
Destination defaultDestination = getDefaultDestination();
if (defaultDestination != null) {
return receiveSelectedAndConvert(defaultDestination, messageSelector, targetClass);
}
else {
return receiveSelectedAndConvert(getRequiredDefaultDestinationName(), messageSelector, targetClass);
}
}
@Override
public <T> @Nullable T receiveSelectedAndConvert(Destination destination, @Nullable String messageSelector,
Class<T> targetClass) throws MessagingException {
Message<?> message = doReceiveSelected(destination, messageSelector);
if (message != null) {
return doConvert(message, targetClass);
}
else {
return null;
}
}
@Override
public <T> @Nullable T receiveSelectedAndConvert(String destinationName, @Nullable String messageSelector,
Class<T> 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<Destination> @@ -277,7 +359,9 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
@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<Destination> @@ -289,7 +373,7 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
@Override
public <T> @Nullable T convertSendAndReceive(Object request, Class<T> targetClass) {
public <T> @Nullable T convertSendAndReceive(Object request, Class<T> targetClass) throws MessagingException {
return convertSendAndReceive(request, targetClass, null);
}
@ -301,7 +385,9 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination> @@ -301,7 +385,9 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
@Override
public <T> @Nullable T convertSendAndReceive(Object request, Class<T> targetClass, @Nullable MessagePostProcessor postProcessor) {
public <T> @Nullable T convertSendAndReceive(Object request, Class<T> 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<Destination> @@ -320,14 +406,16 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
@SuppressWarnings("unchecked")
@Override
public <T> @Nullable T convertSendAndReceive(String destinationName, Object request, @Nullable Map<String, Object> headers,
Class<T> targetClass, @Nullable MessagePostProcessor postProcessor) {
public <T> @Nullable T convertSendAndReceive(String destinationName, Object request,
@Nullable Map<String, Object> headers, Class<T> 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<Destination> @@ -368,6 +456,26 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
}
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 {

98
spring-jms/src/main/java/org/springframework/jms/core/JmsTemplate.java

@ -35,6 +35,7 @@ import org.jspecify.annotations.Nullable; @@ -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; @@ -47,10 +48,9 @@ import org.springframework.util.ClassUtils;
/**
* Helper class that simplifies synchronous JMS access code.
*
* <p>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.
* <p>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.
*
* <p>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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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".
* <p>This is only a hint to the JMS producer.
@ -345,7 +352,6 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations @@ -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 @@ -447,7 +453,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations
* Set the time-to-live of the message when sending.
* <p>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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1178,6 +1191,7 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations
}
}
private abstract static class MicrometerInstrumentation {
static Session instrumentSession(Session session, ObservationRegistry registry) {

513
spring-jms/src/test/java/org/springframework/jms/core/JmsClientTests.java

@ -0,0 +1,513 @@ @@ -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> 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<String> 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<String> 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<String, Object> 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<String, Object> 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<String> 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<String> 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<String> createTextMessage(String payload) {
return MessageBuilder.withPayload(payload).setHeader("foo", "bar").build();
}
private Message<String> 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<TextMessage>) invocation ->
new StubTextMessage((String) invocation.getArguments()[0]));
jakarta.jms.Message message = creator.createMessage(mock);
verify(mock).createTextMessage(any());
return (TextMessage) message;
}
}

167
spring-jms/src/test/java/org/springframework/jms/core/JmsMessagingTemplateTests.java

@ -64,6 +64,7 @@ import static org.mockito.Mockito.verify; @@ -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 { @@ -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 { @@ -170,19 +169,21 @@ class JmsMessagingTemplateTests {
void sendNoDefaultSet() {
Message<String> 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<String> 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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -444,9 +564,7 @@ class JmsMessagingTemplateTests {
@Test
void sendAndReceiveNoDefaultSet() {
Message<String> message = createTextMessage();
assertThatIllegalStateException().isThrownBy(() ->
this.messagingTemplate.sendAndReceive(message));
assertThatIllegalStateException().isThrownBy(() -> this.messagingTemplate.sendAndReceive(message));
}
@Test
@ -592,6 +710,7 @@ class JmsMessagingTemplateTests { @@ -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 { @@ -600,10 +719,8 @@ class JmsMessagingTemplateTests {
}).given(this.jmsTemplate).send(eq("myQueue"), any());
}
private Message<String> createTextMessage(String payload) {
return MessageBuilder
.withPayload(payload).setHeader("foo", "bar").build();
return MessageBuilder.withPayload(payload).setHeader("foo", "bar").build();
}
private Message<String> createTextMessage() {
@ -620,7 +737,6 @@ class JmsMessagingTemplateTests { @@ -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 { @@ -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(

6
spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java

@ -103,14 +103,14 @@ class JmsTemplateTests { @@ -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;
}

23
spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessageSendingTemplate.java

@ -37,6 +37,7 @@ import org.springframework.util.Assert; @@ -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 <D> the destination type
*/
@ -112,12 +113,17 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin @@ -112,12 +113,17 @@ public abstract class AbstractMessageSendingTemplate<D> 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<String, Object>) null);
convertAndSend(destination, payload, null, null);
}
@Override
public void convertAndSend(Object payload, Map<String, Object> headers) throws MessagingException {
convertAndSend(payload, headers, null);
}
@Override
@ -131,7 +137,7 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin @@ -131,7 +137,7 @@ public abstract class AbstractMessageSendingTemplate<D> 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<D> implements MessageSendin @@ -141,6 +147,13 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin
convertAndSend(destination, payload, null, postProcessor);
}
@Override
public void convertAndSend(Object payload, @Nullable Map<String, Object> headers,
@Nullable MessagePostProcessor postProcessor) throws MessagingException {
convertAndSend(getRequiredDefaultDestination(), payload, null, postProcessor);
}
@Override
public void convertAndSend(D destination, Object payload, @Nullable Map<String, Object> headers,
@Nullable MessagePostProcessor postProcessor) throws MessagingException {
@ -152,10 +165,10 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin @@ -152,10 +165,10 @@ public abstract class AbstractMessageSendingTemplate<D> 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<String, Object> headers,

45
spring-messaging/src/main/java/org/springframework/messaging/core/AbstractMessagingTemplate.java

@ -21,6 +21,7 @@ import java.util.Map; @@ -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; @@ -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 <D> the destination type
*/
@ -36,57 +38,72 @@ public abstract class AbstractMessagingTemplate<D> extends AbstractMessageReceiv @@ -36,57 +38,72 @@ public abstract class AbstractMessagingTemplate<D> extends AbstractMessageReceiv
implements MessageRequestReplyOperations<D> {
@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 <T> @Nullable T convertSendAndReceive(Object request, Class<T> targetClass) throws MessagingException {
return convertSendAndReceive(request, null, targetClass, null);
}
@Override
public <T> @Nullable T convertSendAndReceive(Object request, Class<T> targetClass) {
return convertSendAndReceive(getRequiredDefaultDestination(), request, targetClass);
public <T> @Nullable T convertSendAndReceive(D destination, Object request, Class<T> targetClass) throws MessagingException {
return convertSendAndReceive(destination, request, null, targetClass, null);
}
@Override
public <T> @Nullable T convertSendAndReceive(D destination, Object request, Class<T> targetClass) {
return convertSendAndReceive(destination, request, null, targetClass);
public <T> @Nullable T convertSendAndReceive(Object request, @Nullable Map<String, Object> headers, Class<T> targetClass) throws MessagingException {
return convertSendAndReceive(request, headers, targetClass, null);
}
@Override
public <T> @Nullable T convertSendAndReceive(
D destination, Object request, @Nullable Map<String, Object> headers, Class<T> targetClass) {
D destination, Object request, @Nullable Map<String, Object> headers, Class<T> targetClass)
throws MessagingException {
return convertSendAndReceive(destination, request, headers, targetClass, null);
}
@Override
public <T> @Nullable T convertSendAndReceive(
Object request, Class<T> targetClass, @Nullable MessagePostProcessor postProcessor) {
Object request, Class<T> targetClass, @Nullable MessagePostProcessor postProcessor)
throws MessagingException {
return convertSendAndReceive(getRequiredDefaultDestination(), request, targetClass, postProcessor);
return convertSendAndReceive(request, null, targetClass, postProcessor);
}
@Override
public <T> @Nullable T convertSendAndReceive(D destination, Object request, Class<T> targetClass,
@Nullable MessagePostProcessor postProcessor) {
@Nullable MessagePostProcessor postProcessor) throws MessagingException {
return convertSendAndReceive(destination, request, null, targetClass, postProcessor);
}
@SuppressWarnings("unchecked")
@Override
public <T> @Nullable T convertSendAndReceive(Object request, @Nullable Map<String, Object> headers,
Class<T> targetClass, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
return convertSendAndReceive(getRequiredDefaultDestination(), request, headers, targetClass, postProcessor);
}
@SuppressWarnings("unchecked")
@Override
public <T> @Nullable T convertSendAndReceive(D destination, Object request, @Nullable Map<String, Object> headers,
Class<T> targetClass, @Nullable MessagePostProcessor postProcessor) {
Class<T> 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);
}

39
spring-messaging/src/main/java/org/springframework/messaging/core/MessageRequestReplyOperations.java

@ -28,6 +28,7 @@ import org.springframework.messaging.MessagingException; @@ -28,6 +28,7 @@ import org.springframework.messaging.MessagingException;
*
* @author Mark Fisher
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0
* @param <D> the type of destination
* @see GenericMessagingTemplate
@ -76,6 +77,21 @@ public interface MessageRequestReplyOperations<D> { @@ -76,6 +77,21 @@ public interface MessageRequestReplyOperations<D> {
*/
<T> @Nullable T convertSendAndReceive(D destination, Object request, Class<T> 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
*/
<T> @Nullable T convertSendAndReceive(Object request, @Nullable Map<String, Object> headers, Class<T> 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<D> { @@ -95,7 +111,7 @@ public interface MessageRequestReplyOperations<D> {
/**
* 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<D> { @@ -111,7 +127,7 @@ public interface MessageRequestReplyOperations<D> {
/**
* 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<D> { @@ -127,7 +143,24 @@ public interface MessageRequestReplyOperations<D> {
/**
* 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
*/
<T> @Nullable T convertSendAndReceive(
Object request, @Nullable Map<String, Object> headers, Class<T> 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

38
spring-messaging/src/main/java/org/springframework/messaging/core/MessageSendingOperations.java

@ -28,6 +28,7 @@ import org.springframework.messaging.MessagingException; @@ -28,6 +28,7 @@ import org.springframework.messaging.MessagingException;
*
* @author Mark Fisher
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0
* @param <D> the destination type
*/
@ -63,6 +64,16 @@ public interface MessageSendingOperations<D> { @@ -63,6 +64,16 @@ public interface MessageSendingOperations<D> {
*/
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<String, Object> 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<D> { @@ -77,33 +88,46 @@ public interface MessageSendingOperations<D> {
/**
* 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<String, Object> 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<String, Object> headers,
@Nullable MessagePostProcessor postProcessor) throws MessagingException;

Loading…
Cancel
Save