diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/DestinationResolutionException.java b/spring-messaging/src/main/java/org/springframework/messaging/core/DestinationResolutionException.java
index 3f415d3426..bc34eaef11 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/core/DestinationResolutionException.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/core/DestinationResolutionException.java
@@ -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")
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Header.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Header.java
new file mode 100644
index 0000000000..31ef8cb613
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Header.java
@@ -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.
+ *
+ * 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;
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageBody.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Headers.java
similarity index 68%
rename from spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageBody.java
rename to spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Headers.java
index e233be9ba6..460ef8b70d 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageBody.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Headers.java
@@ -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.
- *
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 {
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java
new file mode 100644
index 0000000000..dfc41afaab
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java
@@ -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.
+ *
+ * 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;
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/ValueConstants.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/ValueConstants.java
new file mode 100644
index 0000000000..962b97cb7b
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/ValueConstants.java
@@ -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.
+ *
+ * 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";
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java
new file mode 100644
index 0000000000..4f17df9276
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java
@@ -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.
+ *
+ * Subclasses define how to do the following:
+ *
+ * - Obtain named value information for a method parameter
+ *
- Resolve names into argument values
+ *
- Handle missing argument values when argument values are required
+ *
- Optionally handle a resolved value
+ *
+ *
+ * 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.
+ *
+ * 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 namedValueInfoCache =
+ new ConcurrentHashMap(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;
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolver.java
new file mode 100644
index 0000000000..c3b7a7153f
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolver.java
@@ -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());
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/HeadersMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/HeadersMethodArgumentResolver.java
new file mode 100644
index 0000000000..3be9f9a48b
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/HeadersMethodArgumentResolver.java
@@ -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:
+ *
+ * - Parameters assignable to {@link Map} annotated with {@link Header @Headers}
+ *
- Parameters of type {@link MessageHeaders}
+ *
- Parameters assignable to {@link MessageHeaderAccessor}
+ *
+ *
+ * @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.");
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageBodyMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageBodyMethodArgumentResolver.java
deleted file mode 100644
index ceed5b7028..0000000000
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageBodyMethodArgumentResolver.java
+++ /dev/null
@@ -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.
- *
- * 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;
- }
-
-}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageHandlingException.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageHandlingException.java
new file mode 100644
index 0000000000..b3de5cb9ad
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageHandlingException.java
@@ -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);
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java
new file mode 100644
index 0000000000..ebff786da9
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java
@@ -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}.
+ *
+ *
+ * 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;
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandler.java
index 0bc6f0d254..df54a07557 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandler.java
@@ -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 messageMethods = new HashMap();
@@ -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 converters = new ArrayList();
+ 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.
+ *
+ * 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));
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java
index 54ed13c3e8..db4f8de95a 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java
@@ -49,19 +49,18 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
/**
* A constructor for creating new headers, accepting an optional native header map.
*/
- public NativeMessageHeaderAccessor(Map> nativeHeaders) {
+ protected NativeMessageHeaderAccessor(Map> 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> 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 toMap() {
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..b54cbf7580
--- /dev/null
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeaderMethodArgumentResolverTests.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.messaging.support.MessageBuilder;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Test fixture for {@link HeaderMethodArgumentResolver} tests.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public class HeaderMethodArgumentResolverTests {
+
+ private HeaderMethodArgumentResolver resolver;
+
+ private MethodParameter paramRequired;
+ private MethodParameter paramNamedDefaultValueStringHeader;
+ private MethodParameter paramSystemProperty;
+ private MethodParameter paramNotAnnotated;
+
+
+ @Before
+ public void setup() throws Exception {
+ @SuppressWarnings("resource")
+ GenericApplicationContext cxt = new GenericApplicationContext();
+ cxt.refresh();
+ this.resolver = new HeaderMethodArgumentResolver(new DefaultConversionService(), cxt.getBeanFactory());
+
+ Method method = getClass().getDeclaredMethod("handleMessage",
+ String.class, String.class, String.class, String.class);
+ this.paramRequired = new MethodParameter(method, 0);
+ this.paramNamedDefaultValueStringHeader = new MethodParameter(method, 1);
+ this.paramSystemProperty = new MethodParameter(method, 2);
+ this.paramNotAnnotated = new MethodParameter(method, 3);
+
+ this.paramRequired.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
+ GenericTypeResolver.resolveParameterType(this.paramRequired, HeaderMethodArgumentResolver.class);
+ }
+
+ @Test
+ public void supportsParameter() {
+ assertTrue(resolver.supportsParameter(paramNamedDefaultValueStringHeader));
+ assertFalse(resolver.supportsParameter(paramNotAnnotated));
+ }
+
+ @Test
+ public void resolveArgument() throws Exception {
+ Message message = MessageBuilder.withPayload(new byte[0]).setHeader("param1", "foo").build();
+ this.resolver.resolveArgument(this.paramRequired, message);
+ }
+
+ @Test(expected = MessageHandlingException.class)
+ public void resolveArgumentNotFound() throws Exception {
+ Message message = MessageBuilder.withPayload(new byte[0]).build();
+ this.resolver.resolveArgument(this.paramRequired, message);
+ }
+
+ @Test
+ public void resolveArgumentDefaultValue() throws Exception {
+ Message message = MessageBuilder.withPayload(new byte[0]).build();
+ Object result = this.resolver.resolveArgument(this.paramNamedDefaultValueStringHeader, message);
+
+ assertEquals("bar", result);
+ }
+
+ @Test
+ public void resolveDefaultValueSystemProperty() throws Exception {
+ System.setProperty("systemProperty", "sysbar");
+ try {
+ Message message = MessageBuilder.withPayload(new byte[0]).build();
+ Object result = resolver.resolveArgument(paramSystemProperty, message);
+ assertEquals("sysbar", result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+ }
+
+
+ @SuppressWarnings("unused")
+ private void handleMessage(
+ @Header String param1,
+ @Header(value = "name", defaultValue = "bar") String param2,
+ @Header(value = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
+ String param4) {
+ }
+
+}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeadersMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeadersMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..0f6b32ae04
--- /dev/null
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/HeadersMethodArgumentResolverTests.java
@@ -0,0 +1,146 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.MethodParameter;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.handler.annotation.Headers;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.messaging.support.MessageHeaderAccessor;
+import org.springframework.messaging.support.NativeMessageHeaderAccessor;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Test fixture for {@link HeadersMethodArgumentResolver} tests.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public class HeadersMethodArgumentResolverTests {
+
+ private HeadersMethodArgumentResolver resolver;
+
+ private MethodParameter paramAnnotated;
+ private MethodParameter paramAnnotatedNotMap;
+ private MethodParameter paramMessageHeaders;
+ private MethodParameter paramMessageHeaderAccessor;
+ private MethodParameter paramMessageHeaderAccessorSubclass;
+
+ private Message message;
+
+
+ @Before
+ public void setup() throws Exception {
+ this.resolver = new HeadersMethodArgumentResolver();
+
+ Method method = getClass().getDeclaredMethod("handleMessage", Map.class, String.class,
+ MessageHeaders.class, MessageHeaderAccessor.class, TestMessageHeaderAccessor.class);
+
+ this.paramAnnotated = new MethodParameter(method, 0);
+ this.paramAnnotatedNotMap = new MethodParameter(method, 1);
+ this.paramMessageHeaders = new MethodParameter(method, 2);
+ this.paramMessageHeaderAccessor = new MethodParameter(method, 3);
+ this.paramMessageHeaderAccessorSubclass = new MethodParameter(method, 4);
+
+ Map headers = new HashMap();
+ headers.put("foo", "bar");
+ this.message = MessageBuilder.withPayload(new byte[0]).copyHeaders(headers).build();
+ }
+
+ @Test
+ public void supportsParameter() {
+ assertTrue(this.resolver.supportsParameter(this.paramAnnotated));
+ assertFalse(this.resolver.supportsParameter(this.paramAnnotatedNotMap));
+ assertTrue(this.resolver.supportsParameter(this.paramMessageHeaders));
+ assertTrue(this.resolver.supportsParameter(this.paramMessageHeaderAccessor));
+ assertTrue(this.resolver.supportsParameter(this.paramMessageHeaderAccessorSubclass));
+ }
+
+ @Test
+ public void resolveArgumentAnnotated() throws Exception {
+ Object resolved = this.resolver.resolveArgument(this.paramAnnotated, this.message);
+
+ assertTrue(resolved instanceof Map);
+ @SuppressWarnings("unchecked")
+ Map headers = (Map) resolved;
+ assertEquals("bar", headers.get("foo"));
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void resolveArgumentAnnotatedNotMap() throws Exception {
+ this.resolver.resolveArgument(this.paramAnnotatedNotMap, this.message);
+ }
+
+ @Test
+ public void resolveArgumentMessageHeaders() throws Exception {
+ Object resolved = this.resolver.resolveArgument(this.paramMessageHeaders, this.message);
+
+ assertTrue(resolved instanceof MessageHeaders);
+ MessageHeaders headers = (MessageHeaders) resolved;
+ assertEquals("bar", headers.get("foo"));
+ }
+
+ @Test
+ public void resolveArgumentMessageHeaderAccessor() throws Exception {
+ Object resolved = this.resolver.resolveArgument(this.paramMessageHeaderAccessor, this.message);
+
+ assertTrue(resolved instanceof MessageHeaderAccessor);
+ MessageHeaderAccessor headers = (MessageHeaderAccessor) resolved;
+ assertEquals("bar", headers.getHeader("foo"));
+ }
+
+ @Test
+ public void resolveArgumentMessageHeaderAccessorSubclass() throws Exception {
+ Object resolved = this.resolver.resolveArgument(this.paramMessageHeaderAccessorSubclass, this.message);
+
+ assertTrue(resolved instanceof TestMessageHeaderAccessor);
+ TestMessageHeaderAccessor headers = (TestMessageHeaderAccessor) resolved;
+ assertEquals("bar", headers.getHeader("foo"));
+ }
+
+
+ @SuppressWarnings("unused")
+ private void handleMessage(
+ @Headers Map param1,
+ @Headers String param2,
+ MessageHeaders param3,
+ MessageHeaderAccessor param4,
+ TestMessageHeaderAccessor param5) {
+ }
+
+
+ public static class TestMessageHeaderAccessor extends NativeMessageHeaderAccessor {
+
+ protected TestMessageHeaderAccessor(Message> message) {
+ super(message);
+ }
+
+ public static TestMessageHeaderAccessor wrap(Message> message) {
+ return new TestMessageHeaderAccessor(message);
+ }
+ }
+
+}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolverTests.java
new file mode 100644
index 0000000000..f14b6708b2
--- /dev/null
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolverTests.java
@@ -0,0 +1,85 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.MethodParameter;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.handler.annotation.Payload;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.messaging.support.converter.MessageConverter;
+import org.springframework.messaging.support.converter.StringMessageConverter;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Test fixture for {@link PayloadArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class PayloadArgumentResolverTests {
+
+ private PayloadArgumentResolver resolver;
+
+ private MethodParameter param;
+ private MethodParameter paramNotRequired;
+
+
+ @Before
+ public void setup() throws Exception {
+
+ MessageConverter messageConverter = new StringMessageConverter();
+ this.resolver = new PayloadArgumentResolver(messageConverter );
+
+ Method method = PayloadArgumentResolverTests.class.getDeclaredMethod("handleMessage",
+ String.class, String.class);
+
+ this.param = new MethodParameter(method , 0);
+ this.paramNotRequired = new MethodParameter(method , 1);
+ }
+
+
+ @Test
+ public void resolveRequired() throws Exception {
+ Message> message = MessageBuilder.withPayload("ABC".getBytes()).build();
+ Object actual = this.resolver.resolveArgument(this.param, message);
+
+ assertEquals("ABC", actual);
+ }
+
+ @Test
+ public void resolveNotRequired() throws Exception {
+
+ Message> emptyByteArrayMessage = MessageBuilder.withPayload(new byte[0]).build();
+ assertNull(this.resolver.resolveArgument(this.paramNotRequired, emptyByteArrayMessage));
+
+ Message> notEmptyMessage = MessageBuilder.withPayload("ABC".getBytes()).build();
+ assertEquals("ABC", this.resolver.resolveArgument(this.paramNotRequired, notEmptyMessage));
+ }
+
+
+ @SuppressWarnings("unused")
+ private void handleMessage(
+ @Payload String param,
+ @Payload(required=false) String paramNotRequired) {
+ }
+
+}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandlerTests.java
index a875448cbc..ab31216a2b 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandlerTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/handler/AnnotationMethodMessageHandlerTests.java
@@ -16,15 +16,26 @@
package org.springframework.messaging.simp.handler;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Controller;
+import static org.junit.Assert.*;
+
/**
* Test fixture for {@link AnnotationMethodMessageHandler}.
@@ -32,24 +43,76 @@ import org.springframework.stereotype.Controller;
*/
public class AnnotationMethodMessageHandlerTests {
+ private TestAnnotationMethodMessageHandler messageHandler;
+
+ private TestController testController;
+
+
+ @Before
+ public void setup() {
+ MessageChannel channel = Mockito.mock(MessageChannel.class);
+ SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
+ this.messageHandler = new TestAnnotationMethodMessageHandler(brokerTemplate, channel);
+ this.messageHandler.setApplicationContext(new StaticApplicationContext());
+ this.messageHandler.afterPropertiesSet();
+
+ testController = new TestController();
+ this.messageHandler.registerHandler(testController);
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void headerArgumentResolution() {
+ SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
+ headers.setDestination("/headers");
+ headers.setHeader("foo", "bar");
+ Message> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
+ this.messageHandler.handleMessage(message);
+
+ assertEquals("headers", this.testController.method);
+ assertEquals("bar", this.testController.arguments.get("foo"));
+ assertEquals("bar", ((Map) this.testController.arguments.get("headers")).get("foo"));
+ }
@Test(expected=IllegalStateException.class)
public void duplicateMappings() {
+ this.messageHandler.registerHandler(new DuplicateMappingController());
+ }
- StaticApplicationContext cxt = new StaticApplicationContext();
- cxt.registerSingleton("d", DuplicateMappingController.class);
- cxt.refresh();
- MessageChannel channel = Mockito.mock(MessageChannel.class);
- SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
- AnnotationMethodMessageHandler mh = new AnnotationMethodMessageHandler(brokerTemplate, channel);
- mh.setApplicationContext(cxt);
- mh.afterPropertiesSet();
+ private static class TestAnnotationMethodMessageHandler extends AnnotationMethodMessageHandler {
+
+ public TestAnnotationMethodMessageHandler(SimpMessageSendingOperations brokerTemplate,
+ MessageChannel webSocketResponseChannel) {
+
+ super(brokerTemplate, webSocketResponseChannel);
+ }
+
+ public void registerHandler(Object handler) {
+ super.detectHandlerMethods(handler);
+ }
}
@Controller
- static class DuplicateMappingController {
+ private static class TestController {
+
+ private String method;
+
+ private Map arguments = new LinkedHashMap();
+
+
+ @MessageMapping("/headers")
+ public void headers(@Header String foo, @Headers Map headers) {
+ this.method = "headers";
+ this.arguments.put("foo", foo);
+ this.arguments.put("headers", headers);
+ }
+ }
+
+ @Controller
+ private static class DuplicateMappingController {
@MessageMapping(value="/duplicate")
public void handle1() { }
diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java
index fa511ad3f0..dfd4fe7252 100644
--- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java
@@ -16,12 +16,6 @@
package org.springframework.web.method.annotation;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
import java.lang.reflect.Method;
import java.util.Map;
@@ -37,7 +31,8 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.support.GenericWebApplicationContext;
-import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
+
+import static org.junit.Assert.*;
/**
* Test fixture with {@link org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver}.
@@ -143,7 +138,6 @@ public class RequestHeaderMethodArgumentResolverTests {
@Test(expected = ServletRequestBindingException.class)
public void notFound() throws Exception {
resolver.resolveArgument(paramNamedValueStringArray, null, webRequest, null);
- fail("Expected exception");
}
public void params(@RequestHeader(value = "name", defaultValue = "bar") String param1,