mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Make nullability contracts in JMS SimpleMessageConverter explicit
TextMessage.getText() and ObjectMessage.getObject() may both return null per the JMS specification when the message body was never set, but SimpleMessageConverter.fromMessage() and its parent MessageConverter interface currently declare a non-null return type despite residing in an @NullMarked package. To address that, this commit updates MessageConverter.fromMessage(), SimpleMessageConverter, and the protected extractStringFromMessage()/extractSerializableFromMessage() methods to declare @Nullable accordingly and propagate the resulting nullability through MessagingMessageConverter and AbstractAdaptableMessageListener/MessageListenerAdapter, raising a clear MessageConversionException where a non-null payload is required by the Message<T> contract. Closes gh-37148
This commit is contained in:
+7
-3
@@ -235,10 +235,10 @@ public abstract class AbstractAdaptableMessageListener
|
||||
* Extract the message body from the given JMS message.
|
||||
* @param message the JMS {@code Message}
|
||||
* @return the content of the message, to be passed into the listener method
|
||||
* as an argument
|
||||
* as an argument, or {@code null} if the message has no body
|
||||
* @throws MessageConversionException if the message could not be extracted
|
||||
*/
|
||||
protected Object extractMessage(Message message) {
|
||||
protected @Nullable Object extractMessage(Message message) {
|
||||
try {
|
||||
MessageConverter converter = getMessageConverter();
|
||||
if (converter != null) {
|
||||
@@ -462,7 +462,7 @@ public abstract class AbstractAdaptableMessageListener
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object extractPayload(Message message) throws JMSException {
|
||||
protected @Nullable Object extractPayload(Message message) throws JMSException {
|
||||
Object payload = extractMessage(message);
|
||||
if (message instanceof BytesMessage bytesMessage) {
|
||||
try {
|
||||
@@ -532,6 +532,10 @@ public abstract class AbstractAdaptableMessageListener
|
||||
if (payload instanceof org.springframework.messaging.Message springMessage) {
|
||||
return springMessage.getPayload();
|
||||
}
|
||||
if (payload == null) {
|
||||
throw new MessageConversionException(
|
||||
"Cannot convert JMS message with no body to a Message with a payload: " + this.message);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
+16
-13
@@ -106,9 +106,9 @@ import org.springframework.util.ObjectUtils;
|
||||
* String handleMessage(String text);
|
||||
* }</pre>
|
||||
*
|
||||
* For further examples and discussion please do refer to the Spring
|
||||
* reference documentation which describes this class (and it's attendant
|
||||
* XML configuration) in detail.
|
||||
* <p>For further examples and discussion, please refer to the Spring reference
|
||||
* documentation which describes this class (and its corresponding configuration)
|
||||
* in detail.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
@@ -219,7 +219,7 @@ public class MessageListenerAdapter extends AbstractAdaptableMessageListener imp
|
||||
String methodName = getListenerMethodName(message, convertedMessage);
|
||||
|
||||
// Invoke the handler method with appropriate arguments.
|
||||
Object[] listenerArguments = buildListenerArguments(convertedMessage);
|
||||
@Nullable Object[] listenerArguments = buildListenerArguments(convertedMessage);
|
||||
Object result = invokeListenerMethod(methodName, listenerArguments);
|
||||
if (result != null) {
|
||||
handleResult(result, message, session);
|
||||
@@ -247,12 +247,12 @@ public class MessageListenerAdapter extends AbstractAdaptableMessageListener imp
|
||||
* default listener method, if any.
|
||||
* @param originalMessage the JMS request message
|
||||
* @param extractedMessage the converted JMS request message,
|
||||
* to be passed into the listener method as argument
|
||||
* to be passed into the listener method as an argument
|
||||
* @return the name of the listener method (never {@code null})
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #setDefaultListenerMethod
|
||||
*/
|
||||
protected String getListenerMethodName(Message originalMessage, Object extractedMessage) throws JMSException {
|
||||
protected String getListenerMethodName(Message originalMessage, @Nullable Object extractedMessage) throws JMSException {
|
||||
return getDefaultListenerMethod();
|
||||
}
|
||||
|
||||
@@ -260,31 +260,34 @@ public class MessageListenerAdapter extends AbstractAdaptableMessageListener imp
|
||||
* Build an array of arguments to be passed into the target listener method.
|
||||
* Allows for multiple method arguments to be built from a single message object.
|
||||
* <p>The default implementation builds an array with the given message object
|
||||
* as sole element. This means that the extracted message will always be passed
|
||||
* as its sole element. This means that the extracted message will always be passed
|
||||
* into a <i>single</i> method argument, even if it is an array, with the target
|
||||
* method having a corresponding single argument of the array's type declared.
|
||||
* <p>This can be overridden to treat special message content such as arrays
|
||||
* differently, for example passing in each element of the message array
|
||||
* as distinct method argument.
|
||||
* @param extractedMessage the content of the message
|
||||
* as a distinct method argument.
|
||||
* @param extractedMessage the content of the message, which may be
|
||||
* {@code null} if the message had no body
|
||||
* @return the array of arguments to be passed into the
|
||||
* listener method (each element of the array corresponding
|
||||
* to a distinct method argument)
|
||||
* to a distinct method argument); an element may be {@code null}
|
||||
* if the corresponding extracted message content was {@code null}
|
||||
*/
|
||||
protected Object[] buildListenerArguments(Object extractedMessage) {
|
||||
protected @Nullable Object[] buildListenerArguments(@Nullable Object extractedMessage) {
|
||||
return new Object[] {extractedMessage};
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the specified listener method.
|
||||
* @param methodName the name of the listener method
|
||||
* @param arguments the message arguments to be passed in
|
||||
* @param arguments the message arguments to be passed in (an element
|
||||
* may be {@code null})
|
||||
* @return the result returned from the listener method
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #getListenerMethodName
|
||||
* @see #buildListenerArguments
|
||||
*/
|
||||
protected @Nullable Object invokeListenerMethod(String methodName, Object[] arguments) throws JMSException {
|
||||
protected @Nullable Object invokeListenerMethod(String methodName, @Nullable Object[] arguments) throws JMSException {
|
||||
try {
|
||||
MethodInvoker methodInvoker = new MethodInvoker();
|
||||
methodInvoker.setTargetObject(getDelegate());
|
||||
|
||||
+5
-2
@@ -19,6 +19,7 @@ package org.springframework.jms.support.converter;
|
||||
import jakarta.jms.JMSException;
|
||||
import jakarta.jms.Message;
|
||||
import jakarta.jms.Session;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Strategy interface that specifies a converter between Java objects and JMS messages.
|
||||
@@ -48,10 +49,12 @@ public interface MessageConverter {
|
||||
/**
|
||||
* Convert from a JMS Message to a Java object.
|
||||
* @param message the message to convert
|
||||
* @return the converted Java object
|
||||
* @return the converted Java object, or {@code null} if the message has no
|
||||
* body (for example, a {@link jakarta.jms.TextMessage} or
|
||||
* {@link jakarta.jms.ObjectMessage} whose body was never set)
|
||||
* @throws jakarta.jms.JMSException if thrown by JMS API methods
|
||||
* @throws MessageConversionException in case of conversion failure
|
||||
*/
|
||||
Object fromMessage(Message message) throws JMSException, MessageConversionException;
|
||||
@Nullable Object fromMessage(Message message) throws JMSException, MessageConversionException;
|
||||
|
||||
}
|
||||
|
||||
+5
-1
@@ -117,6 +117,9 @@ public class MessagingMessageConverter implements MessageConverter, Initializing
|
||||
public Object fromMessage(jakarta.jms.Message message) throws JMSException, MessageConversionException {
|
||||
Map<String, Object> mappedHeaders = extractHeaders(message);
|
||||
Object convertedObject = extractPayload(message);
|
||||
if (convertedObject == null) {
|
||||
throw new MessageConversionException("Cannot convert JMS message with no body to a Message with a payload");
|
||||
}
|
||||
MessageBuilder<Object> builder = (convertedObject instanceof org.springframework.messaging.Message springMessage ?
|
||||
MessageBuilder.fromMessage(springMessage) : MessageBuilder.withPayload(convertedObject));
|
||||
return builder.copyHeadersIfAbsent(mappedHeaders).build();
|
||||
@@ -124,8 +127,9 @@ public class MessagingMessageConverter implements MessageConverter, Initializing
|
||||
|
||||
/**
|
||||
* Extract the payload of the specified {@link jakarta.jms.Message}.
|
||||
* @return the extracted payload, or {@code null} if the message has no body
|
||||
*/
|
||||
protected Object extractPayload(jakarta.jms.Message message) throws JMSException {
|
||||
protected @Nullable Object extractPayload(jakarta.jms.Message message) throws JMSException {
|
||||
return this.payloadConverter.fromMessage(message);
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -28,6 +28,7 @@ import jakarta.jms.Message;
|
||||
import jakarta.jms.ObjectMessage;
|
||||
import jakarta.jms.Session;
|
||||
import jakarta.jms.TextMessage;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@@ -91,13 +92,15 @@ public class SimpleMessageConverter implements MessageConverter {
|
||||
* ByteMessage back to a byte array, a MapMessage back to a Map,
|
||||
* and an ObjectMessage back to a Serializable object. Returns
|
||||
* the plain Message object in case of an unknown message type.
|
||||
* <p>Note that a {@code TextMessage} or {@code ObjectMessage} whose body was
|
||||
* never set is converted to {@code null}, per the JMS specification.
|
||||
* @see #extractStringFromMessage
|
||||
* @see #extractByteArrayFromMessage
|
||||
* @see #extractMapFromMessage
|
||||
* @see #extractSerializableFromMessage
|
||||
*/
|
||||
@Override
|
||||
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
|
||||
public @Nullable Object fromMessage(Message message) throws JMSException, MessageConversionException {
|
||||
if (message instanceof TextMessage textMessage) {
|
||||
return extractStringFromMessage(textMessage);
|
||||
}
|
||||
@@ -179,10 +182,10 @@ public class SimpleMessageConverter implements MessageConverter {
|
||||
/**
|
||||
* Extract a String from the given TextMessage.
|
||||
* @param message the message to convert
|
||||
* @return the resulting String
|
||||
* @return the resulting String, or {@code null} if the message body was never set
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
protected String extractStringFromMessage(TextMessage message) throws JMSException {
|
||||
protected @Nullable String extractStringFromMessage(TextMessage message) throws JMSException {
|
||||
return message.getText();
|
||||
}
|
||||
|
||||
@@ -218,10 +221,11 @@ public class SimpleMessageConverter implements MessageConverter {
|
||||
/**
|
||||
* Extract a Serializable object from the given {@link ObjectMessage}.
|
||||
* @param message the message to convert
|
||||
* @return the resulting Serializable object
|
||||
* @return the resulting Serializable object, or {@code null} if the message body
|
||||
* was never set
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
protected Serializable extractSerializableFromMessage(ObjectMessage message) throws JMSException {
|
||||
protected @Nullable Serializable extractSerializableFromMessage(ObjectMessage message) throws JMSException {
|
||||
return message.getObject();
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -64,6 +64,15 @@ class SimpleMessageConverterTests {
|
||||
assertThat(converter.fromMessage(msg)).isEqualTo(content);
|
||||
}
|
||||
|
||||
@Test // gh-37148
|
||||
void stringConversionForMessageWithNoTextReturnsNull() throws JMSException {
|
||||
TextMessage message = mock();
|
||||
given(message.getText()).willReturn(null);
|
||||
|
||||
SimpleMessageConverter converter = new SimpleMessageConverter();
|
||||
assertThat(converter.fromMessage(message)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void byteArrayConversion() throws JMSException {
|
||||
Session session = mock();
|
||||
@@ -121,6 +130,15 @@ class SimpleMessageConverterTests {
|
||||
assertThat(converter.fromMessage(msg)).isEqualTo(content);
|
||||
}
|
||||
|
||||
@Test // gh-37148
|
||||
void serializableConversionForMessageWithNoObjectReturnsNull() throws JMSException {
|
||||
ObjectMessage message = mock();
|
||||
given(message.getObject()).willReturn(null);
|
||||
|
||||
SimpleMessageConverter converter = new SimpleMessageConverter();
|
||||
assertThat(converter.fromMessage(message)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void toMessageThrowsExceptionIfGivenNullObjectToConvert() {
|
||||
assertThatExceptionOfType(MessageConversionException.class).isThrownBy(() ->
|
||||
|
||||
Reference in New Issue
Block a user