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:
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user