PayloadArgumentResolver supports JsonView as well (through AbstractMessageConverter revision)
AbstractMessageConverter provides overloaded methods with a conversion hint, MappingJackson2MessageConverter takes that hint into account, and SimpMessagingTemplate transformes such a hint in the given headers map into an explicit argument invocation argument. Issue: SPR-13265
This commit is contained in:
parent
24b1dc14d2
commit
0f54f686b2
|
@ -24,7 +24,6 @@ import java.util.List;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
@ -40,20 +39,11 @@ import org.springframework.util.MimeType;
|
|||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sebastien Deleuze
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.0
|
||||
*/
|
||||
public abstract class AbstractMessageConverter implements MessageConverter {
|
||||
|
||||
/**
|
||||
* Name of the header that can be set to provide further information
|
||||
* ({@link MethodParameter} instance) about the origin of the payload (for
|
||||
* {@link #toMessage(Object, MessageHeaders)}) or about the target of the payload
|
||||
* ({@link #fromMessage(Message, Class)}).
|
||||
* @since 4.2
|
||||
*/
|
||||
public static final String METHOD_PARAMETER_HINT_HEADER = "methodParameterHint";
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final List<MimeType> supportedMimeTypes;
|
||||
|
@ -174,10 +164,27 @@ public abstract class AbstractMessageConverter implements MessageConverter {
|
|||
|
||||
@Override
|
||||
public final Object fromMessage(Message<?> message, Class<?> targetClass) {
|
||||
return fromMessage(message, targetClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of {@link #fromMessage(Message, Class)} which takes an extra
|
||||
* conversion context as an argument, allowing to take e.g. annotations
|
||||
* on a payload parameter into account.
|
||||
* @param message the input message
|
||||
* @param targetClass the target class for the conversion
|
||||
* @param conversionHint an extra object passed to the {@link MessageConverter},
|
||||
* e.g. the associated {@code MethodParameter} (may be {@code null}}
|
||||
* @return the result of the conversion, or {@code null} if the converter cannot
|
||||
* perform the conversion
|
||||
* @since 4.2
|
||||
* @see #fromMessage(Message, Class)
|
||||
*/
|
||||
public final Object fromMessage(Message<?> message, Class<?> targetClass, Object conversionHint) {
|
||||
if (!canConvertFrom(message, targetClass)) {
|
||||
return null;
|
||||
}
|
||||
return convertFromInternal(message, targetClass);
|
||||
return convertFromInternal(message, targetClass, conversionHint);
|
||||
}
|
||||
|
||||
protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
|
||||
|
@ -186,13 +193,33 @@ public abstract class AbstractMessageConverter implements MessageConverter {
|
|||
|
||||
@Override
|
||||
public final Message<?> toMessage(Object payload, MessageHeaders headers) {
|
||||
return toMessage(payload, headers, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of {@link #toMessage(Object, MessageHeaders)} which takes an extra
|
||||
* conversion context as an argument, allowing to take e.g. annotations
|
||||
* on a return type into account.
|
||||
* @param payload the Object to convert
|
||||
* @param headers optional headers for the message (may be {@code null})
|
||||
* @param conversionHint an extra object passed to the {@link MessageConverter},
|
||||
* e.g. the associated {@code MethodParameter} (may be {@code null}}
|
||||
* @return the new message, or {@code null} if the converter does not support the
|
||||
* Object type or the target media type
|
||||
* @since 4.2
|
||||
* @see #toMessage(Object, MessageHeaders)
|
||||
*/
|
||||
public final Message<?> toMessage(Object payload, MessageHeaders headers, Object conversionHint) {
|
||||
if (!canConvertTo(payload, headers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
payload = convertToInternal(payload, headers);
|
||||
MimeType mimeType = getDefaultContentType(payload);
|
||||
payload = convertToInternal(payload, headers, conversionHint);
|
||||
if (payload == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MimeType mimeType = getDefaultContentType(payload);
|
||||
if (headers != null) {
|
||||
MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(headers, MessageHeaderAccessor.class);
|
||||
if (accessor != null && accessor.isMutable()) {
|
||||
|
@ -244,13 +271,52 @@ public abstract class AbstractMessageConverter implements MessageConverter {
|
|||
|
||||
/**
|
||||
* Convert the message payload from serialized form to an Object.
|
||||
* @param message the input message
|
||||
* @param targetClass the target class for the conversion
|
||||
* @param conversionHint an extra object passed to the {@link MessageConverter},
|
||||
* e.g. the associated {@code MethodParameter} (may be {@code null}}
|
||||
* @return the result of the conversion, or {@code null} if the converter cannot
|
||||
* perform the conversion
|
||||
* @since 4.2
|
||||
*/
|
||||
public abstract Object convertFromInternal(Message<?> message, Class<?> targetClass);
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
|
||||
return convertFromInternal(message, targetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the payload object to serialized form.
|
||||
* @param payload the Object to convert
|
||||
* @param headers optional headers for the message (may be {@code null})
|
||||
* @param conversionHint an extra object passed to the {@link MessageConverter},
|
||||
* e.g. the associated {@code MethodParameter} (may be {@code null}}
|
||||
* @return the resulting payload for the message, or {@code null} if the converter
|
||||
* cannot perform the conversion
|
||||
* @since 4.2
|
||||
*/
|
||||
public abstract Object convertToInternal(Object payload, MessageHeaders headers);
|
||||
@SuppressWarnings("deprecation")
|
||||
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
|
||||
return convertToInternal(payload, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the message payload from serialized form to an Object.
|
||||
* @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
|
||||
* (which is also protected instead of public)
|
||||
*/
|
||||
@Deprecated
|
||||
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the payload object to serialized form.
|
||||
* @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
|
||||
* (which is also protected instead of public)
|
||||
*/
|
||||
@Deprecated
|
||||
public Object convertToInternal(Object payload, MessageHeaders headers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -29,7 +29,6 @@ import org.springframework.util.MimeTypeUtils;
|
|||
*/
|
||||
public class ByteArrayMessageConverter extends AbstractMessageConverter {
|
||||
|
||||
|
||||
public ByteArrayMessageConverter() {
|
||||
super(MimeTypeUtils.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
@ -37,16 +36,16 @@ public class ByteArrayMessageConverter extends AbstractMessageConverter {
|
|||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return byte[].class == clazz;
|
||||
return (byte[].class == clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
|
||||
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
|
||||
return message.getPayload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertToInternal(Object payload, MessageHeaders headers) {
|
||||
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
|
|
@ -195,15 +195,28 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
|
||||
@SuppressWarnings("deprecation")
|
||||
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
|
||||
JavaType javaType = this.objectMapper.constructType(targetClass);
|
||||
Object payload = message.getPayload();
|
||||
Class<?> view = getSerializationView(conversionHint);
|
||||
// Note: in the view case, calling withType instead of forType for compatibility with Jackson <2.5
|
||||
try {
|
||||
if (payload instanceof byte[]) {
|
||||
return this.objectMapper.readValue((byte[]) payload, javaType);
|
||||
if (view != null) {
|
||||
return this.objectMapper.readerWithView(view).withType(javaType).readValue((byte[]) payload);
|
||||
}
|
||||
else {
|
||||
return this.objectMapper.readValue((byte[]) payload, javaType);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return this.objectMapper.readValue((String) payload, javaType);
|
||||
if (view != null) {
|
||||
return this.objectMapper.readerWithView(view).withType(javaType).readValue(payload.toString());
|
||||
}
|
||||
else {
|
||||
return this.objectMapper.readValue(payload.toString(), javaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
|
@ -212,15 +225,15 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convertToInternal(Object payload, MessageHeaders headers) {
|
||||
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
|
||||
try {
|
||||
Class<?> serializationView = getSerializationView(headers);
|
||||
Class<?> view = getSerializationView(conversionHint);
|
||||
if (byte[].class == getSerializedPayloadClass()) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
JsonEncoding encoding = getJsonEncoding(getMimeType(headers));
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding);
|
||||
if (serializationView != null) {
|
||||
this.objectMapper.writerWithView(serializationView).writeValue(generator, payload);
|
||||
if (view != null) {
|
||||
this.objectMapper.writerWithView(view).writeValue(generator, payload);
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(generator, payload);
|
||||
|
@ -229,8 +242,8 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
|
|||
}
|
||||
else {
|
||||
Writer writer = new StringWriter();
|
||||
if (serializationView != null) {
|
||||
this.objectMapper.writerWithView(serializationView).writeValue(writer, payload);
|
||||
if (view != null) {
|
||||
this.objectMapper.writerWithView(view).writeValue(writer, payload);
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(writer, payload);
|
||||
|
@ -244,20 +257,40 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
|
|||
return payload;
|
||||
}
|
||||
|
||||
private Class<?> getSerializationView(MessageHeaders headers) {
|
||||
MethodParameter returnType = (headers != null ?
|
||||
(MethodParameter) headers.get(METHOD_PARAMETER_HINT_HEADER) : null);
|
||||
if (returnType == null) {
|
||||
return null;
|
||||
}
|
||||
JsonView annotation = returnType.getMethodAnnotation(JsonView.class);
|
||||
if (annotation == null) {
|
||||
/**
|
||||
* Determine a Jackson serialization view based on the given conversion hint.
|
||||
* @param conversionHint the conversion hint Object as passed into the
|
||||
* converter for the current conversion attempt
|
||||
* @return the serialization view class, or {@code null} if none
|
||||
*/
|
||||
protected Class<?> getSerializationView(Object conversionHint) {
|
||||
if (conversionHint instanceof MethodParameter) {
|
||||
MethodParameter methodParam = (MethodParameter) conversionHint;
|
||||
JsonView annotation = methodParam.getParameterAnnotation(JsonView.class);
|
||||
if (annotation == null) {
|
||||
annotation = methodParam.getMethodAnnotation(JsonView.class);
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return extractViewClass(annotation, conversionHint);
|
||||
}
|
||||
else if (conversionHint instanceof JsonView) {
|
||||
return extractViewClass((JsonView) conversionHint, conversionHint);
|
||||
}
|
||||
else if (conversionHint instanceof Class) {
|
||||
return (Class) conversionHint;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> extractViewClass(JsonView annotation, Object conversionHint) {
|
||||
Class<?>[] classes = annotation.value();
|
||||
if (classes.length != 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"@JsonView only supported for handler methods with exactly 1 class argument: " + returnType);
|
||||
"@JsonView only supported for handler methods with exactly 1 class argument: " + conversionHint);
|
||||
}
|
||||
return classes[0];
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ public class MarshallingMessageConverter extends AbstractMessageConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
|
||||
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
|
||||
Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required");
|
||||
try {
|
||||
Source source = getSource(message.getPayload());
|
||||
|
@ -159,7 +159,7 @@ public class MarshallingMessageConverter extends AbstractMessageConverter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object convertToInternal(Object payload, MessageHeaders headers) {
|
||||
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
|
||||
Assert.notNull(this.marshaller, "Property 'marshaller' is required");
|
||||
try {
|
||||
if (byte[].class == getSerializedPayloadClass()) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -46,18 +46,18 @@ public class StringMessageConverter extends AbstractMessageConverter {
|
|||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return String.class == clazz;
|
||||
return (String.class == clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
|
||||
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
|
||||
Charset charset = getContentTypeCharset(getMimeType(message.getHeaders()));
|
||||
Object payload = message.getPayload();
|
||||
return (payload instanceof String) ? payload : new String((byte[]) payload, charset);
|
||||
return (payload instanceof String ? payload : new String((byte[]) payload, charset));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertToInternal(Object payload, MessageHeaders headers) {
|
||||
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
|
||||
if (byte[].class == getSerializedPayloadClass()) {
|
||||
Charset charset = getContentTypeCharset(getMimeType(headers));
|
||||
payload = ((String) payload).getBytes(charset);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
|
|||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConversionException;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.converter.SimpleMessageConverter;
|
||||
|
@ -39,6 +40,15 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
public abstract class AbstractMessageSendingTemplate<D> implements MessageSendingOperations<D> {
|
||||
|
||||
/**
|
||||
* Name of the header that can be set to provide further information
|
||||
* (e.g. a {@code MethodParameter} instance) about the origin of the
|
||||
* payload, to be taken into account as a conversion hint.
|
||||
* @since 4.2
|
||||
*/
|
||||
public static final String CONVERSION_HINT_HEADER = "conversionHint";
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private volatile D defaultDestination;
|
||||
|
@ -144,6 +154,8 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin
|
|||
*/
|
||||
protected Message<?> doConvert(Object payload, Map<String, Object> headers, MessagePostProcessor postProcessor) {
|
||||
MessageHeaders messageHeaders = null;
|
||||
Object conversionHint = (headers != null ? headers.get(CONVERSION_HINT_HEADER) : null);
|
||||
|
||||
Map<String, Object> headersToUse = processHeadersToSend(headers);
|
||||
if (headersToUse != null) {
|
||||
if (headersToUse instanceof MessageHeaders) {
|
||||
|
@ -154,7 +166,10 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin
|
|||
}
|
||||
}
|
||||
|
||||
Message<?> message = getMessageConverter().toMessage(payload, messageHeaders);
|
||||
MessageConverter converter = getMessageConverter();
|
||||
Message<?> message = (converter instanceof AbstractMessageConverter ?
|
||||
((AbstractMessageConverter) converter).toMessage(payload, messageHeaders, conversionHint) :
|
||||
converter.toMessage(payload, messageHeaders));
|
||||
if (message == null) {
|
||||
String payloadType = (payload != null ? payload.getClass().getName() : null);
|
||||
Object contentType = (messageHeaders != null ? messageHeaders.get(MessageHeaders.CONTENT_TYPE) : null);
|
||||
|
@ -168,11 +183,11 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin
|
|||
}
|
||||
|
||||
/**
|
||||
* Provides access to the map of input headers before a send operation. Sub-classes
|
||||
* can modify the headers and then return the same or a different map.
|
||||
* Provides access to the map of input headers before a send operation.
|
||||
* Subclasses can modify the headers and then return the same or a different map.
|
||||
* <p>This default implementation in this class returns the input map.
|
||||
* @param headers the headers to send or {@code null}
|
||||
* @return the actual headers to send or {@code null}
|
||||
* @param headers the headers to send (or {@code null} if none)
|
||||
* @return the actual headers to send (or {@code null} if none)
|
||||
*/
|
||||
protected Map<String, Object> processHeadersToSend(Map<String, Object> headers) {
|
||||
return headers;
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.lang.annotation.Annotation;
|
|||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConversionException;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
|
@ -85,8 +86,8 @@ public class PayloadArgumentResolver implements HandlerMethodArgumentResolver {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter param, Message<?> message) throws Exception {
|
||||
Payload ann = param.getParameterAnnotation(Payload.class);
|
||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
|
||||
Payload ann = parameter.getParameterAnnotation(Payload.class);
|
||||
if (ann != null && StringUtils.hasText(ann.expression())) {
|
||||
throw new IllegalStateException("@Payload SpEL expressions not supported by this resolver");
|
||||
}
|
||||
|
@ -94,28 +95,30 @@ public class PayloadArgumentResolver implements HandlerMethodArgumentResolver {
|
|||
Object payload = message.getPayload();
|
||||
if (isEmptyPayload(payload)) {
|
||||
if (ann == null || ann.required()) {
|
||||
String paramName = getParameterName(param);
|
||||
String paramName = getParameterName(parameter);
|
||||
BindingResult bindingResult = new BeanPropertyBindingResult(payload, paramName);
|
||||
bindingResult.addError(new ObjectError(paramName, "@Payload param is required"));
|
||||
throw new MethodArgumentNotValidException(message, param, bindingResult);
|
||||
throw new MethodArgumentNotValidException(message, parameter, bindingResult);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> targetClass = param.getParameterType();
|
||||
Class<?> targetClass = parameter.getParameterType();
|
||||
if (ClassUtils.isAssignable(targetClass, payload.getClass())) {
|
||||
validate(message, param, payload);
|
||||
validate(message, parameter, payload);
|
||||
return payload;
|
||||
}
|
||||
else {
|
||||
payload = this.converter.fromMessage(message, targetClass);
|
||||
payload = (this.converter instanceof AbstractMessageConverter ?
|
||||
((AbstractMessageConverter) this.converter).fromMessage(message, targetClass, parameter) :
|
||||
this.converter.fromMessage(message, targetClass));
|
||||
if (payload == null) {
|
||||
throw new MessageConversionException(message,
|
||||
"No converter found to convert to " + targetClass + ", message=" + message);
|
||||
}
|
||||
validate(message, param, payload);
|
||||
validate(message, parameter, payload);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.springframework.core.annotation.AnnotationUtils;
|
|||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver;
|
||||
|
@ -33,6 +32,7 @@ import org.springframework.messaging.handler.invocation.HandlerMethodReturnValue
|
|||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.messaging.simp.annotation.SendToUser;
|
||||
import org.springframework.messaging.simp.user.DestinationUserNameProvider;
|
||||
import org.springframework.messaging.support.MessageHeaderInitializer;
|
||||
|
@ -220,7 +220,7 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
|
|||
if (sessionId != null) {
|
||||
headerAccessor.setSessionId(sessionId);
|
||||
}
|
||||
headerAccessor.setHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType);
|
||||
headerAccessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType);
|
||||
headerAccessor.setLeaveMutable(true);
|
||||
return headerAccessor.getMessageHeaders();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -22,12 +22,12 @@ import org.apache.commons.logging.LogFactory;
|
|||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.core.MessageSendingOperations;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.messaging.simp.annotation.SendToUser;
|
||||
import org.springframework.messaging.simp.annotation.SubscribeMapping;
|
||||
import org.springframework.messaging.support.MessageHeaderInitializer;
|
||||
|
@ -121,7 +121,7 @@ public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturn
|
|||
}
|
||||
headerAccessor.setSessionId(sessionId);
|
||||
headerAccessor.setSubscriptionId(subscriptionId);
|
||||
headerAccessor.setHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType);
|
||||
headerAccessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType);
|
||||
headerAccessor.setLeaveMutable(true);
|
||||
return headerAccessor.getMessageHeaders();
|
||||
}
|
||||
|
|
|
@ -74,8 +74,7 @@ public class MappingJackson2MessageConverterTests {
|
|||
@Test
|
||||
public void fromMessage() throws Exception {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],"
|
||||
+ "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(UTF_8)).build();
|
||||
MyBean actual = (MyBean) converter.fromMessage(message, MyBean.class);
|
||||
|
||||
|
@ -178,20 +177,25 @@ public class MappingJackson2MessageConverterTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void jsonView() throws Exception {
|
||||
public void toMessageJsonView() throws Exception {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Method method = this.getClass().getDeclaredMethod("handle");
|
||||
Method method = getClass().getDeclaredMethod("jsonViewResponse");
|
||||
MethodParameter returnType = new MethodParameter(method, -1);
|
||||
map.put(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER, returnType);
|
||||
MessageHeaders headers = new MessageHeaders(map);
|
||||
Message<?> message = converter.toMessage(handle(), headers);
|
||||
Message<?> message = converter.toMessage(jsonViewResponse(), new MessageHeaders(map), returnType);
|
||||
String actual = new String((byte[]) message.getPayload(), UTF_8);
|
||||
|
||||
assertThat(actual, containsString("\"withView1\":\"with\""));
|
||||
assertThat(actual, not(containsString("\"withView2\":\"with\"")));
|
||||
assertThat(actual, not(containsString("\"withoutView\":\"without\"")));
|
||||
assertThat(actual, containsString("\"withView2\":\"with\""));
|
||||
assertThat(actual, not(containsString("\"withoutView\":\"with\"")));
|
||||
|
||||
method = getClass().getDeclaredMethod("jsonViewPayload", JacksonViewBean.class);
|
||||
MethodParameter param = new MethodParameter(method, 0);
|
||||
JacksonViewBean back = (JacksonViewBean) converter.fromMessage(message, JacksonViewBean.class, param);
|
||||
assertNull(back.getWithView1());
|
||||
assertEquals("with", back.getWithView2());
|
||||
assertNull(back.getWithoutView());
|
||||
}
|
||||
|
||||
|
||||
|
@ -266,7 +270,7 @@ public class MappingJackson2MessageConverterTests {
|
|||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
@JsonView({MyJacksonView1.class, MyJacksonView2.class})
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
@ -297,12 +301,15 @@ public class MappingJackson2MessageConverterTests {
|
|||
}
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
public JacksonViewBean handle() {
|
||||
public JacksonViewBean jsonViewResponse() {
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
bean.setWithoutView("with");
|
||||
return bean;
|
||||
}
|
||||
|
||||
public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.springframework.core.annotation.SynthesizingMethodParameter;
|
|||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.converter.StringMessageConverter;
|
||||
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
|
||||
|
@ -143,7 +142,6 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void sendToNoAnnotations() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app", "/dest", null);
|
||||
|
@ -156,12 +154,11 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertEquals("/topic/dest", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.noAnnotationsReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.noAnnotationsReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendTo() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -175,19 +172,18 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertEquals("/dest1", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.sendToReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
|
||||
accessor = getCapturedAccessor(1);
|
||||
assertEquals(sessionId, accessor.getSessionId());
|
||||
assertEquals("/dest2", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.sendToReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToDefaultDestination() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -201,12 +197,11 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertEquals("/topic/dest", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToDefaultDestReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.sendToDefaultDestReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToDefaultDestinationWhenUsingDotPathSeparator() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app/", "dest.foo.bar", null);
|
||||
|
@ -220,7 +215,6 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void testHeadersToSend() throws Exception {
|
||||
|
||||
Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app", "/dest", null);
|
||||
|
||||
SimpMessageSendingOperations messagingTemplate = Mockito.mock(SimpMessageSendingOperations.class);
|
||||
|
@ -237,12 +231,11 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertTrue(accessor.isMutable());
|
||||
assertEquals("sess1", accessor.getSessionId());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.noAnnotationsReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.noAnnotationsReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToUser() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -263,11 +256,8 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertEquals("/user/" + user.getName() + "/dest2", accessor.getDestination());
|
||||
}
|
||||
|
||||
// SPR-12170
|
||||
|
||||
@Test
|
||||
@Test // SPR-12170
|
||||
public void sendToWithDestinationPlaceholders() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
Map<String, String> vars = new LinkedHashMap<>(1);
|
||||
|
@ -290,7 +280,6 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void sendToUserSingleSession() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -305,19 +294,18 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertEquals("/user/" + user.getName() + "/dest1", accessor.getDestination());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
|
||||
accessor = getCapturedAccessor(1);
|
||||
assertEquals(sessionId, accessor.getSessionId());
|
||||
assertEquals("/user/" + user.getName() + "/dest2", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.sendToUserSingleSessionReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToUserWithUserNameProvider() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -336,7 +324,6 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void sendToUserDefaultDestination() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -354,7 +341,6 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void sendToUserDefaultDestinationWhenUsingDotPathSeparator() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
TestUser user = new TestUser();
|
||||
|
@ -369,7 +355,6 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void sendToUserDefaultDestinationSingleSession() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -384,12 +369,11 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertEquals("/user/" + user.getName() + "/queue/dest", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToUserSingleSessionDefaultDestReturnType, accessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.sendToUserSingleSessionDefaultDestReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToUserSessionWithoutUserName() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -409,7 +393,6 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void jsonView() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -420,7 +403,7 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
Message<?> message = this.messageCaptor.getValue();
|
||||
assertNotNull(message);
|
||||
|
||||
assertEquals("{\"withView1\":\"with\"}", new String((byte[])message.getPayload(), StandardCharsets.UTF_8));
|
||||
assertEquals("{\"withView1\":\"with\"}", new String((byte[]) message.getPayload(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
|
@ -515,8 +498,8 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
}
|
||||
|
||||
|
||||
private interface MyJacksonView1 {};
|
||||
private interface MyJacksonView2 {};
|
||||
private interface MyJacksonView1 {}
|
||||
private interface MyJacksonView2 {}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class JacksonViewBean {
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.springframework.core.MethodParameter;
|
|||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.converter.StringMessageConverter;
|
||||
import org.springframework.messaging.core.MessageSendingOperations;
|
||||
|
@ -82,7 +81,6 @@ public class SubscriptionMethodReturnValueHandlerTests {
|
|||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(this.messageChannel);
|
||||
|
@ -116,7 +114,6 @@ public class SubscriptionMethodReturnValueHandlerTests {
|
|||
|
||||
@Test
|
||||
public void testMessageSentToChannel() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -138,13 +135,12 @@ public class SubscriptionMethodReturnValueHandlerTests {
|
|||
assertEquals(subscriptionId, headerAccessor.getSubscriptionId());
|
||||
assertEquals(destination, headerAccessor.getDestination());
|
||||
assertEquals(MIME_TYPE, headerAccessor.getContentType());
|
||||
assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void testHeadersPassedToMessagingTemplate() throws Exception {
|
||||
|
||||
String sessionId = "sess1";
|
||||
String subscriptionId = "subs1";
|
||||
String destination = "/dest";
|
||||
|
@ -165,12 +161,11 @@ public class SubscriptionMethodReturnValueHandlerTests {
|
|||
assertTrue(headerAccessor.isMutable());
|
||||
assertEquals(sessionId, headerAccessor.getSessionId());
|
||||
assertEquals(subscriptionId, headerAccessor.getSubscriptionId());
|
||||
assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(AbstractMessageConverter.METHOD_PARAMETER_HINT_HEADER));
|
||||
assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonView() throws Exception {
|
||||
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
|
@ -184,7 +179,7 @@ public class SubscriptionMethodReturnValueHandlerTests {
|
|||
Message<?> message = this.messageCaptor.getValue();
|
||||
assertNotNull(message);
|
||||
|
||||
assertEquals("{\"withView1\":\"with\"}", new String((byte[])message.getPayload(), StandardCharsets.UTF_8));
|
||||
assertEquals("{\"withView1\":\"with\"}", new String((byte[]) message.getPayload(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue