Support Message argument with payload conversion

The MessageMethodArgumentResolver now also supports applying a
MessageConverter to the payload.

This is effectively a shortcut for declaring a method with an @Payload
argument + MessageHeaders and then creating a new message from the two.

Issue: SPR-13288
This commit is contained in:
Rossen Stoyanchev
2016-02-18 18:06:13 -05:00
parent ec3571fd4d
commit 416966d943
5 changed files with 133 additions and 27 deletions

View File

@@ -158,7 +158,7 @@ public class DefaultMessageHandlerMethodFactory implements MessageHandlerMethodF
resolvers.add(new HeadersMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new MessageMethodArgumentResolver());
resolvers.add(new MessageMethodArgumentResolver(this.messageConverter));
if (this.customArgumentResolvers != null) {
resolvers.addAll(this.customArgumentResolvers);

View File

@@ -21,8 +21,14 @@ import java.lang.reflect.Type;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.SmartMessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@code HandlerMethodArgumentResolver} for {@link Message} method arguments.
@@ -34,6 +40,19 @@ import org.springframework.util.ClassUtils;
*/
public class MessageMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final MessageConverter converter;
/**
* Create a new instance with the given {@link MessageConverter}.
* @param converter the MessageConverter to use (required)
*/
public MessageMethodArgumentResolver(MessageConverter converter) {
Assert.notNull(converter, "MessageConverter must not be null");
this.converter = converter;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Message.class.isAssignableFrom(parameter.getParameterType());
@@ -41,23 +60,32 @@ public class MessageMethodArgumentResolver implements HandlerMethodArgumentResol
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (!paramType.isAssignableFrom(message.getClass())) {
Class<?> targetMessageType = parameter.getParameterType();
Class<?> targetPayloadType = getPayloadType(parameter);
if (!targetMessageType.isAssignableFrom(message.getClass())) {
String actual = ClassUtils.getQualifiedName(message.getClass());
String expected = ClassUtils.getQualifiedName(paramType);
String expected = ClassUtils.getQualifiedName(targetMessageType);
throw new MethodArgumentTypeMismatchException(message, parameter, "The actual message type " +
"[" + actual + "] does not match the expected type [" + expected + "]");
}
Class<?> targetPayloadType = getPayloadType(parameter);
Object payload = message.getPayload();
if (payload != null && !targetPayloadType.isInstance(payload)) {
throw new MethodArgumentTypeMismatchException(message, parameter,
"The expected Message<?> payload type [" + ClassUtils.getQualifiedName(targetPayloadType) +
"] does not match the actual payload type [" + ClassUtils.getQualifiedName(payload.getClass()) + "]");
if (payload == null || targetPayloadType.isInstance(payload)) {
return message;
}
return message;
if (isEmptyPayload(payload)) {
String actual = ClassUtils.getQualifiedName(payload.getClass());
String expected = ClassUtils.getQualifiedName(targetPayloadType);
throw new MessageConversionException(message, "Cannot convert from the " +
"expected payload type [" + expected + "] to the " +
"actual payload type [" + actual + "] when the payload is empty.");
}
payload = convertPayload(message, parameter, targetPayloadType);
return MessageBuilder.createMessage(payload, message.getHeaders());
}
private Class<?> getPayloadType(MethodParameter parameter) {
@@ -66,4 +94,42 @@ public class MessageMethodArgumentResolver implements HandlerMethodArgumentResol
return resolvableType.getGeneric(0).resolve(Object.class);
}
/**
* Check if the given {@code payload} is empty.
* @param payload the payload to check (can be {@code null})
*/
protected boolean isEmptyPayload(Object payload) {
if (payload == null) {
return true;
}
else if (payload instanceof byte[]) {
return ((byte[]) payload).length == 0;
}
else if (payload instanceof String) {
return !StringUtils.hasText((String) payload);
}
else {
return false;
}
}
private Object convertPayload(Message<?> message, MethodParameter parameter, Class<?> targetPayloadType) {
Object result;
if (this.converter instanceof SmartMessageConverter) {
SmartMessageConverter smartConverter = (SmartMessageConverter) this.converter;
result = smartConverter.fromMessage(message, targetPayloadType, parameter);
}
else {
result = this.converter.fromMessage(message, targetPayloadType);
}
if (result == null) {
String actual = ClassUtils.getQualifiedName(targetPayloadType);
String expected = ClassUtils.getQualifiedName(message.getPayload().getClass());
throw new MessageConversionException(message, "No converter found to convert payload " +
"type [" + actual + "] to expected payload type [" + expected + "].");
}
return result;
}
}

View File

@@ -317,7 +317,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
// Type-based argument resolution
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new MessageMethodArgumentResolver());
resolvers.add(new MessageMethodArgumentResolver(this.messageConverter));
resolvers.addAll(getCustomArgumentResolvers());
resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator));

View File

@@ -26,11 +26,15 @@ import org.junit.rules.ExpectedException;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageBuilder;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for
@@ -43,20 +47,27 @@ public class MessageMethodArgumentResolverTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final MessageMethodArgumentResolver resolver = new MessageMethodArgumentResolver();
private MessageConverter converter;
private MessageMethodArgumentResolver resolver;
private Method method;
@Before
public void setup() throws Exception {
this.method = MessageMethodArgumentResolverTests.class.getDeclaredMethod("handleMessage",
Message.class, Message.class, Message.class, Message.class, ErrorMessage.class);
this.method = MessageMethodArgumentResolverTests.class.getDeclaredMethod("handle",
Message.class, Message.class, Message.class, Message.class,
ErrorMessage.class);
this.converter = mock(MessageConverter.class);
this.resolver = new MessageMethodArgumentResolver(this.converter);
}
@Test
public void resolveAnyPayloadType() throws Exception {
public void resolveWithPayloadTypeAsWildcard() throws Exception {
Message<String> message = MessageBuilder.withPayload("test").build();
MethodParameter parameter = new MethodParameter(this.method, 0);
@@ -65,7 +76,7 @@ public class MessageMethodArgumentResolverTests {
}
@Test
public void resolvePayloadTypeExactType() throws Exception {
public void resolveWithMatchingPayloadType() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build();
MethodParameter parameter = new MethodParameter(this.method, 1);
@@ -74,7 +85,7 @@ public class MessageMethodArgumentResolverTests {
}
@Test
public void resolvePayloadTypeSubClass() throws Exception {
public void resolveWithPayloadTypeSubClass() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build();
MethodParameter parameter = new MethodParameter(this.method, 2);
@@ -83,19 +94,47 @@ public class MessageMethodArgumentResolverTests {
}
@Test
public void resolveInvalidPayloadType() throws Exception {
public void resolveWithConversion() throws Exception {
Message<String> message = MessageBuilder.withPayload("test").build();
MethodParameter parameter = new MethodParameter(this.method, 1);
when(this.converter.fromMessage(message, Integer.class)).thenReturn(4);
@SuppressWarnings("unchecked")
Message<Integer> actual = (Message<Integer>) this.resolver.resolveArgument(parameter, message);
assertNotNull(actual);
assertSame(message.getHeaders(), actual.getHeaders());
assertEquals(new Integer(4), actual.getPayload());
}
@Test
public void resolveWithConversionNoMatchingConverter() throws Exception {
Message<String> message = MessageBuilder.withPayload("test").build();
MethodParameter parameter = new MethodParameter(this.method, 1);
assertTrue(this.resolver.supportsParameter(parameter));
thrown.expect(MethodArgumentTypeMismatchException.class);
thrown.expect(MessageConversionException.class);
thrown.expectMessage(Integer.class.getName());
thrown.expectMessage(String.class.getName());
this.resolver.resolveArgument(parameter, message);
}
@Test
public void resolveUpperBoundPayloadType() throws Exception {
public void resolveWithConversionEmptyPayload() throws Exception {
Message<String> message = MessageBuilder.withPayload("").build();
MethodParameter parameter = new MethodParameter(this.method, 1);
assertTrue(this.resolver.supportsParameter(parameter));
thrown.expect(MessageConversionException.class);
thrown.expectMessage("the payload is empty");
thrown.expectMessage(Integer.class.getName());
thrown.expectMessage(String.class.getName());
this.resolver.resolveArgument(parameter, message);
}
@Test
public void resolveWithPayloadTypeUpperBound() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build();
MethodParameter parameter = new MethodParameter(this.method, 3);
@@ -104,19 +143,19 @@ public class MessageMethodArgumentResolverTests {
}
@Test
public void resolveOutOfBoundPayloadType() throws Exception {
public void resolveWithPayloadTypeOutOfBound() throws Exception {
Message<Locale> message = MessageBuilder.withPayload(Locale.getDefault()).build();
MethodParameter parameter = new MethodParameter(this.method, 3);
assertTrue(this.resolver.supportsParameter(parameter));
thrown.expect(MethodArgumentTypeMismatchException.class);
thrown.expect(MessageConversionException.class);
thrown.expectMessage(Number.class.getName());
thrown.expectMessage(Locale.class.getName());
this.resolver.resolveArgument(parameter, message);
}
@Test
public void resolveMessageSubTypeExactMatch() throws Exception {
public void resolveMessageSubClassMatch() throws Exception {
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException());
MethodParameter parameter = new MethodParameter(this.method, 4);
@@ -125,7 +164,7 @@ public class MessageMethodArgumentResolverTests {
}
@Test
public void resolveMessageSubTypeSubClass() throws Exception {
public void resolveWithMessageSubClassAndPayloadWildcard() throws Exception {
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException());
MethodParameter parameter = new MethodParameter(this.method, 0);
@@ -134,7 +173,7 @@ public class MessageMethodArgumentResolverTests {
}
@Test
public void resolveWrongMessageType() throws Exception {
public void resolveWithWrongMessageType() throws Exception {
UnsupportedOperationException ex = new UnsupportedOperationException();
Message<? extends Throwable> message = new GenericMessage<Throwable>(ex);
MethodParameter parameter = new MethodParameter(this.method, 4);
@@ -148,7 +187,7 @@ public class MessageMethodArgumentResolverTests {
@SuppressWarnings("unused")
private void handleMessage(
private void handle(
Message<?> wildcardPayload,
Message<Integer> integerPayload,
Message<Number> numberPayload,

View File

@@ -34,6 +34,7 @@ import org.junit.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.MethodIntrospector;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.SimpleMessageConverter;
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
import org.springframework.messaging.handler.HandlerMethod;
import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
@@ -196,7 +197,7 @@ public class MethodMessageHandlerTests {
@Override
protected List<? extends HandlerMethodArgumentResolver> initArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add(new MessageMethodArgumentResolver());
resolvers.add(new MessageMethodArgumentResolver(new SimpleMessageConverter()));
resolvers.addAll(getCustomArgumentResolvers());
return resolvers;
}