Add support for resolving message headers

This change adds support for @Header and @Headers annotated method
arguments to spring-messaging. Also supported are arguments of type
MessageHeaders, and MessageHeaderAccessor (including sub-types of
MessageHeaderAccessort as long as they provide a wrap(Message<?>)
static factory method).

This change also renames @MessageBody to @Payload.

Issue: SPR-10985
This commit is contained in:
Rossen Stoyanchev
2013-10-13 11:04:22 -04:00
parent ca9df4d2ef
commit 8ae88c20d1
18 changed files with 1155 additions and 112 deletions

View File

@@ -19,9 +19,10 @@ package org.springframework.messaging.core;
import org.springframework.messaging.MessagingException;
/**
* Thrown by a ChannelResolver when it cannot resolve a channel name.
* Thrown by a {@link DestinationResolver} when it cannot resolve a destination.
*
* @author Mark Fisher
* @author Rossen Stoyanchev
* @since 4.0
*/
@SuppressWarnings("serial")

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2012 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
*
* http://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.messaging.handler.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation which indicates that a method parameter should be bound to a message header.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Header {
/**
* The name of the request header to bind to.
*/
String value() default "";
/**
* Whether the header is required.
* <p>
* Default is {@code true}, leading to an exception if the header missing. Switch this
* to {@code false} if you prefer a {@code null} in case of the header missing.
*/
boolean required() default true;
/**
* The default value to use as a fallback. Supplying a default value implicitly
* sets {@link #required} to {@code false}.
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}

View File

@@ -22,12 +22,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.messaging.Message;
/**
* Annotation indicating a method parameter should be bound to the body of a
* {@link Message}.
* Annotation which indicates that a method parameter should be bound to the headers of a
* message. The annotated parameter must be assignable to {@link java.util.Map} with
* String keys and Object values.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -35,14 +34,6 @@ import org.springframework.messaging.Message;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MessageBody {
/**
* Whether body content is required.
* <p>Default is {@code true}, leading to an exception thrown in case
* there is no body content. Switch this to {@code false} if you prefer
* {@code null} to be passed when the body content is {@code null}.
*/
boolean required() default true;
public @interface Headers {
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2012 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
*
* http://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.messaging.handler.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.messaging.support.converter.MessageConverter;
/**
* Annotation that binds a method parameter to the payload of a message. The payload may
* be passed through a {@link MessageConverter} to convert it from serialized form with a
* specific MIME type to an Object matching the target method parameter.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Payload {
/**
* Whether payload content is required.
* <p>
* Default is {@code true}, leading to an exception if there is no payload. Switch to
* {@code false} to have {@code null} passed when there is no payload.
*/
boolean required() default true;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2002-2012 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
*
* http://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.messaging.handler.annotation;
/**
* Common annotation value constants.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface ValueConstants {
/**
* Constant defining a value for no default - as a replacement for {@code null} which
* we cannot use in annotation attributes.
* <p>
* This is an artificial arrangement of 16 unicode characters, with its sole purpose
* being to never match user-declared values.
*
* @see Header#defaultValue()
*/
String DEFAULT_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n";
}

View File

@@ -0,0 +1,240 @@
/*
* Copyright 2002-2013 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
*
* http://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.messaging.handler.annotation.support;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.ValueConstants;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Abstract base class for resolving method arguments from a named value. Message headers,
* and path variables are examples of named values. Each may have a name, a required flag,
* and a default value.
* <p>
* Subclasses define how to do the following:
* <ul>
* <li>Obtain named value information for a method parameter
* <li>Resolve names into argument values
* <li>Handle missing argument values when argument values are required
* <li>Optionally handle a resolved value
* </ul>
* <p>
* A default value string can contain ${...} placeholders and Spring Expression Language
* #{...} expressions. For this to work a {@link ConfigurableBeanFactory} must be supplied
* to the class constructor.
* <p>
* A {@link ConversionService} may be used to apply type conversion to the resolved
* argument value if it doesn't match the method parameter type.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final ConfigurableBeanFactory configurableBeanFactory;
private final BeanExpressionContext expressionContext;
private Map<MethodParameter, NamedValueInfo> namedValueInfoCache =
new ConcurrentHashMap<MethodParameter, NamedValueInfo>(256);
private ConversionService conversionService;
/**
* Constructor with a {@link ConversionService} and a {@link BeanFactory}.
*
* @param cs conversion service for converting values to match the
* target method parameter type
* @param beanFactory a bean factory to use for resolving ${...} placeholder and
* #{...} SpEL expressions in default values, or {@code null} if default values
* are not expected to contain expressions
*/
protected AbstractNamedValueMethodArgumentResolver(ConversionService cs, ConfigurableBeanFactory beanFactory) {
this.conversionService = (cs != null) ? cs : new DefaultConversionService();
this.configurableBeanFactory = beanFactory;
this.expressionContext = (beanFactory != null) ? new BeanExpressionContext(beanFactory, null) : null;
}
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
Class<?> paramType = parameter.getParameterType();
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
Object value = resolveArgumentInternal(parameter, message, namedValueInfo.name);
if (value == null) {
if (namedValueInfo.defaultValue != null) {
value = resolveDefaultValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required) {
handleMissingValue(namedValueInfo.name, parameter, message);
}
value = handleNullValue(namedValueInfo.name, value, paramType);
}
else if ("".equals(value) && (namedValueInfo.defaultValue != null)) {
value = resolveDefaultValue(namedValueInfo.defaultValue);
}
if (!ClassUtils.isAssignableValue(paramType, value)) {
value = this.conversionService.convert(value,
TypeDescriptor.valueOf(value.getClass()), new TypeDescriptor(parameter));
}
handleResolvedValue(value, namedValueInfo.name, parameter, message);
return value;
}
/**
* Obtain the named value for the given method parameter.
*/
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
/**
* Create the {@link NamedValueInfo} object for the given method parameter. Implementations typically
* retrieve the method annotation by means of {@link MethodParameter#getParameterAnnotation(Class)}.
* @param parameter the method parameter
* @return the named value information
*/
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
/**
* Create a new NamedValueInfo based on the given NamedValueInfo with sanitized values.
*/
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
if (info.name.length() == 0) {
name = parameter.getParameterName();
Assert.notNull(name, "Name for argument type [" + parameter.getParameterType().getName()
+ "] not available, and parameter name information not found in class file either.");
}
String defaultValue = ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue;
return new NamedValueInfo(name, info.required, defaultValue);
}
/**
* Resolves the given parameter type and value name into an argument value.
* @param parameter the method parameter to resolve to an argument value
* @param message the current request
* @param name the name of the value being resolved
*
* @return the resolved argument. May be {@code null}
* @throws Exception in case of errors
*/
protected abstract Object resolveArgumentInternal(MethodParameter parameter, Message<?> message, String name)
throws Exception;
/**
* Resolves the given default value into an argument value.
*/
private Object resolveDefaultValue(String defaultValue) {
if (this.configurableBeanFactory == null) {
return defaultValue;
}
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue);
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return defaultValue;
}
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
}
/**
* Invoked when a named value is required, but
* {@link #resolveArgumentInternal(MethodParameter, Message, String)} returned {@code null} and
* there is no default value. Subclasses typically throw an exception in this case.
*
* @param name the name for the value
* @param parameter the method parameter
* @param message the message being processed
*/
protected abstract void handleMissingValue(String name, MethodParameter parameter, Message<?> message);
/**
* A {@code null} results in a {@code false} value for {@code boolean}s or an
* exception for other primitives.
*/
private Object handleNullValue(String name, Object value, Class<?> paramType) {
if (value == null) {
if (Boolean.TYPE.equals(paramType)) {
return Boolean.FALSE;
}
else if (paramType.isPrimitive()) {
throw new IllegalStateException("Optional " + paramType + " parameter '" + name +
"' is present but cannot be translated into a null value due to being " +
"declared as a primitive type. Consider declaring it as object wrapper " +
"for the corresponding primitive type.");
}
}
return value;
}
/**
* Invoked after a value is resolved.
*
* @param arg the resolved argument value
* @param name the argument name
* @param parameter the argument parameter type
* @param message the message
*/
protected void handleResolvedValue(Object arg, String name, MethodParameter parameter, Message<?> message) {
}
/**
* Represents the information about a named value, including name, whether it's
* required and a default value.
*/
protected static class NamedValueInfo {
private final String name;
private final boolean required;
private final String defaultValue;
protected NamedValueInfo(String name, boolean required, String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2013 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
*
* http://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.messaging.handler.annotation.support;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Header;
/**
* Resolves method parameters annotated with {@link Header @Header}.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class HeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public HeaderMethodArgumentResolver(ConversionService cs, ConfigurableBeanFactory beanFactory) {
super(cs, beanFactory);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Header.class);
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
Header annotation = parameter.getParameterAnnotation(Header.class);
return new HeaderNamedValueInfo(annotation);
}
@Override
protected Object resolveArgumentInternal(MethodParameter parameter, Message<?> message,
String name) throws Exception {
return message.getHeaders().get(name);
}
@Override
protected void handleMissingValue(String headerName, MethodParameter parameter, Message<?> message) {
throw new MessageHandlingException(message, "Missing header '" + headerName +
"' for method parameter type [" + parameter.getParameterType() + "]");
}
private static class HeaderNamedValueInfo extends NamedValueInfo {
private HeaderNamedValueInfo(Header annotation) {
super(annotation.value(), annotation.required(), annotation.defaultValue());
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2013 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
*
* http://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.messaging.handler.annotation.support;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Resolves the following method parameters:
* <ul>
* <li>Parameters assignable to {@link Map} annotated with {@link Header @Headers}
* <li>Parameters of type {@link MessageHeaders}
* <li>Parameters assignable to {@link MessageHeaderAccessor}
* </ul>
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class HeadersMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return ((parameter.hasParameterAnnotation(Headers.class) && Map.class.isAssignableFrom(paramType)) ||
MessageHeaders.class.equals(paramType) ||
MessageHeaderAccessor.class.isAssignableFrom(paramType));
}
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (Map.class.isAssignableFrom(paramType)) {
return message.getHeaders();
}
else if (MessageHeaderAccessor.class.equals(paramType)) {
return new MessageHeaderAccessor(message);
}
else if (MessageHeaderAccessor.class.isAssignableFrom(paramType)) {
Method factoryMethod = ClassUtils.getMethod(paramType, "wrap", Message.class);
return ReflectionUtils.invokeMethod(factoryMethod, null, message);
}
else {
throw new IllegalStateException("Unexpected method parameter type "
+ paramType + "in method " + parameter.getMethod() + ". "
+ "@Headers method arguments must be assignable to java.util.Map.");
}
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2002-2013 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
*
* http://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.messaging.handler.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.MessageBody;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
import org.springframework.messaging.support.converter.MessageConverter;
import org.springframework.util.Assert;
/**
* A resolver for extracting the body of a message.
*
* <p>This {@link HandlerMethodArgumentResolver} should be ordered last as it supports all
* types and does not require the {@link MessageBody} annotation.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class MessageBodyMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final MessageConverter converter;
public MessageBodyMethodArgumentResolver(MessageConverter converter) {
Assert.notNull(converter, "converter is required");
this.converter = converter;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return true;
}
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
Object arg = null;
MessageBody annot = parameter.getParameterAnnotation(MessageBody.class);
if (annot == null || annot.required()) {
Class<?> sourceClass = message.getPayload().getClass();
Class<?> targetClass = parameter.getParameterType();
if (targetClass.isAssignableFrom(sourceClass)) {
return message.getPayload();
}
else {
return this.converter.fromMessage(message, targetClass);
}
}
return arg;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2013 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
*
* http://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.messaging.handler.annotation.support;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
/**
* Thrown when the handling of a message results in an unrecoverable exception.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class MessageHandlingException extends MessagingException {
public MessageHandlingException(Message<?> message, String description, Throwable cause) {
super(message, description, cause);
}
public MessageHandlingException(Message<?> message, String description) {
super(message, description);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2013 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
*
* http://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.messaging.handler.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
import org.springframework.messaging.support.converter.MessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A resolver to extract and convert the payload of a message using a
* {@link MessageConverter}.
*
* <p>
* This {@link HandlerMethodArgumentResolver} should be ordered last as it supports all
* types and does not require the {@link Payload} annotation.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class PayloadArgumentResolver implements HandlerMethodArgumentResolver {
private final MessageConverter converter;
public PayloadArgumentResolver(MessageConverter messageConverter) {
Assert.notNull(messageConverter, "converter is required");
this.converter = messageConverter;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return true;
}
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
Class<?> sourceClass = message.getPayload().getClass();
Class<?> targetClass = parameter.getParameterType();
if (ClassUtils.isAssignable(targetClass,sourceClass)) {
return message.getPayload();
}
Payload annot = parameter.getParameterAnnotation(Payload.class);
if (isEmptyPayload(message)) {
if ((annot != null) && !annot.required()) {
return null;
}
}
return this.converter.fromMessage(message, targetClass);
}
protected boolean isEmptyPayload(Message<?> message) {
Object payload = message.getPayload();
if (payload instanceof byte[]) {
return ((byte[]) message.getPayload()).length == 0;
}
else if (payload instanceof String) {
return ((String) payload).trim().equals("");
}
else {
return false;
}
}
}

View File

@@ -31,10 +31,14 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@@ -42,8 +46,10 @@ import org.springframework.messaging.MessagingException;
import org.springframework.messaging.core.AbstractMessageSendingTemplate;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.support.ExceptionHandlerMethodResolver;
import org.springframework.messaging.handler.annotation.support.MessageBodyMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
import org.springframework.messaging.handler.method.HandlerMethod;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolverComposite;
@@ -61,7 +67,10 @@ import org.springframework.messaging.simp.annotation.support.PrincipalMethodArgu
import org.springframework.messaging.simp.annotation.support.SendToMethodReturnValueHandler;
import org.springframework.messaging.simp.annotation.support.SubscriptionMethodReturnValueHandler;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.converter.ByteArrayMessageConverter;
import org.springframework.messaging.support.converter.CompositeMessageConverter;
import org.springframework.messaging.support.converter.MessageConverter;
import org.springframework.messaging.support.converter.StringMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -85,6 +94,8 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
private MessageConverter messageConverter;
private ConversionService conversionService = new DefaultFormattingConversionService();
private ApplicationContext applicationContext;
private Map<MappingInfo, HandlerMethod> messageMethods = new HashMap<MappingInfo, HandlerMethod>();
@@ -116,6 +127,11 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
Assert.notNull(webSocketResponseChannel, "webSocketReplyChannel is required");
this.brokerTemplate = brokerTemplate;
this.webSocketResponseTemplate = new SimpMessagingTemplate(webSocketResponseChannel);
Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
converters.add(new StringMessageConverter());
converters.add(new ByteArrayMessageConverter());
this.messageConverter = new CompositeMessageConverter(converters);
}
/**
@@ -147,6 +163,14 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
return this.destinationPrefixes;
}
/**
* Configure a {@link MessageConverter} to use to convert the payload of a message
* from serialize form with a specific MIME type to an Object matching the target
* method parameter. The converter is also used when sending message to the message
* broker.
*
* @see CompositeMessageConverter
*/
public void setMessageConverter(MessageConverter converter) {
this.messageConverter = converter;
if (converter != null) {
@@ -154,6 +178,30 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
}
}
/**
* Return the configured {@link MessageConverter}.
*/
public MessageConverter getMessageConverter() {
return this.messageConverter;
}
/**
* Configure a {@link ConversionService} to use when resolving method arguments, for
* example message header values.
* <p>
* By default an instance of {@link DefaultFormattingConversionService} is used.
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* The configured {@link ConversionService}.
*/
public ConversionService getConversionService() {
return this.conversionService;
}
/**
* Sets the list of custom {@code HandlerMethodArgumentResolver}s that will be used
* after resolvers for supported argument type.
@@ -176,20 +224,25 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
this.customReturnValueHandlers = customReturnValueHandlers;
}
public MessageConverter getMessageConverter() {
return this.messageConverter;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() {
initHandlerMethods();
ConfigurableBeanFactory beanFactory =
(ClassUtils.isAssignableValue(ConfigurableApplicationContext.class, this.applicationContext)) ?
((ConfigurableApplicationContext) this.applicationContext).getBeanFactory() : null;
// Annotation-based argument resolution
this.argumentResolvers.addResolver(new HeaderMethodArgumentResolver(this.conversionService, beanFactory));
this.argumentResolvers.addResolver(new HeadersMethodArgumentResolver());
// Type-based argument resolution
this.argumentResolvers.addResolver(new PrincipalMethodArgumentResolver());
this.argumentResolvers.addResolver(new MessageMethodArgumentResolver());
@@ -198,7 +251,7 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
this.argumentResolvers.addResolvers(this.customArgumentResolvers);
// catch-all argument resolver
this.argumentResolvers.addResolver(new MessageBodyMethodArgumentResolver(this.messageConverter));
this.argumentResolvers.addResolver(new PayloadArgumentResolver(this.messageConverter));
// Annotation-based return value types
this.returnValueHandlers.addHandler(new SendToMethodReturnValueHandler(this.brokerTemplate, true));

View File

@@ -49,19 +49,18 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
/**
* A constructor for creating new headers, accepting an optional native header map.
*/
public NativeMessageHeaderAccessor(Map<String, List<String>> nativeHeaders) {
protected NativeMessageHeaderAccessor(Map<String, List<String>> nativeHeaders) {
this.originalNativeHeaders = nativeHeaders;
}
/**
* A constructor for accessing and modifying existing message headers.
*/
public NativeMessageHeaderAccessor(Message<?> message) {
protected NativeMessageHeaderAccessor(Message<?> message) {
super(message);
this.originalNativeHeaders = initNativeHeaders(message);
}
private static Map<String, List<String>> initNativeHeaders(Message<?> message) {
if (message != null) {
@SuppressWarnings("unchecked")
@@ -73,6 +72,13 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
return null;
}
/**
* Create {@link NativeMessageHeaderAccessor} from the headers of an existing message.
*/
public static NativeMessageHeaderAccessor wrap(Message<?> message) {
return new NativeMessageHeaderAccessor(message);
}
@Override
public Map<String, Object> toMap() {