diff --git a/spring-web-reactive/build.gradle b/spring-web-reactive/build.gradle
index 76c8db41f9..b0f5771525 100644
--- a/spring-web-reactive/build.gradle
+++ b/spring-web-reactive/build.gradle
@@ -27,7 +27,7 @@ repositories {
}
ext {
- springVersion = '4.3.0.RC1'
+ springVersion = '4.3.0.BUILD-SNAPSHOT'
reactorVersion = '2.5.0.BUILD-SNAPSHOT'
reactorNettyVersion = '2.5.0.BUILD-SNAPSHOT'
tomcatVersion = '8.0.33'
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueMethodArgumentResolver.java
new file mode 100644
index 0000000000..1db3ca782a
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueMethodArgumentResolver.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.ConversionNotSupportedException;
+import org.springframework.beans.SimpleTypeConverter;
+import org.springframework.beans.TypeMismatchException;
+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.ui.ModelMap;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.server.ServerErrorException;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+
+/**
+ * Abstract base class for resolving method arguments from a named value.
+ * Request parameters, request 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.
+ *
+ * @author Rossen Stoyanchev
+ */
+public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
+
+ private final ConfigurableBeanFactory configurableBeanFactory;
+
+ private final BeanExpressionContext expressionContext;
+
+ private final Map namedValueInfoCache = new ConcurrentHashMap<>(256);
+
+ /** Instead of a WebDataBinder for now */
+ private final SimpleTypeConverter typeConverter;
+
+
+ /**
+ * @param conversionService for type conversion (to be replaced with WebDataBinder)
+ * @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
+ */
+ public AbstractNamedValueMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory) {
+
+ Assert.notNull(conversionService, "'conversionService' is required.");
+ this.typeConverter = new SimpleTypeConverter();
+ this.typeConverter.setConversionService(conversionService);
+ this.configurableBeanFactory = beanFactory;
+ this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null);
+ }
+
+
+ @Override
+ public Mono resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
+ NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
+ MethodParameter nestedParameter = parameter.nestedIfOptional();
+
+ Object resolvedName = resolveStringValue(namedValueInfo.name);
+ if (resolvedName == null) {
+ return Mono.error(new IllegalArgumentException(
+ "Specified name must not resolve to null: [" + namedValueInfo.name + "]"));
+ }
+
+ return resolveName(resolvedName.toString(), nestedParameter, exchange)
+ .map(arg -> {
+ if ("".equals(arg) && namedValueInfo.defaultValue != null) {
+ arg = resolveStringValue(namedValueInfo.defaultValue);
+ }
+ arg = applyConversion(arg, parameter);
+ handleResolvedValue(arg, namedValueInfo.name, parameter, model, exchange);
+ return arg;
+ })
+ .otherwiseIfEmpty(getDefaultValue(namedValueInfo, parameter, model, exchange));
+ }
+
+ /**
+ * 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();
+ if (name == null) {
+ String type = parameter.getNestedParameterType().getName();
+ throw new IllegalArgumentException("Name for argument type [" + type + "] 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);
+ }
+
+ /**
+ * Resolve the given annotation-specified value,
+ * potentially containing placeholders and expressions.
+ */
+ private Object resolveStringValue(String value) {
+ if (this.configurableBeanFactory == null) {
+ return value;
+ }
+ String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
+ BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
+ if (exprResolver == null) {
+ return value;
+ }
+ return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
+ }
+
+ /**
+ * Resolve the given parameter type and value name into an argument value.
+ * @param name the name of the value being resolved
+ * @param parameter the method parameter to resolve to an argument value
+ * (pre-nested in case of a {@link java.util.Optional} declaration)
+ * @param exchange the current exchange
+ * @return the resolved argument (may be {@code null})
+ */
+ protected abstract Mono resolveName(String name, MethodParameter parameter,
+ ServerWebExchange exchange);
+
+ private Object applyConversion(Object value, MethodParameter parameter) {
+ try {
+ value = this.typeConverter.convertIfNecessary(value, parameter.getParameterType(), parameter);
+ }
+ catch (ConversionNotSupportedException ex) {
+ throw new ServerErrorException("Conversion not supported.", parameter, ex);
+ }
+ catch (TypeMismatchException ex) {
+ throw new ServerWebInputException("Type mismatch.", parameter, ex);
+ }
+ return value;
+ }
+
+ private Mono getDefaultValue(NamedValueInfo namedValueInfo, MethodParameter parameter,
+ ModelMap model, ServerWebExchange exchange) {
+
+ Object value = null;
+ try {
+ if (namedValueInfo.defaultValue != null) {
+ value = resolveStringValue(namedValueInfo.defaultValue);
+ }
+ else if (namedValueInfo.required && !parameter.isOptional()) {
+ handleMissingValue(namedValueInfo.name, parameter, exchange);
+ }
+ value = handleNullValue(namedValueInfo.name, value, parameter.getNestedParameterType());
+ value = applyConversion(value, parameter);
+ handleResolvedValue(value, namedValueInfo.name, parameter, model, exchange);
+ return Mono.justOrEmpty(value);
+ }
+ catch (Throwable ex) {
+ return Mono.error(ex);
+ }
+ }
+
+ /**
+ * Invoked when a named value is required, but
+ * {@link #resolveName(String, MethodParameter, ServerWebExchange)} 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 exchange the current exchange
+ */
+ @SuppressWarnings("UnusedParameters")
+ protected void handleMissingValue(String name, MethodParameter parameter, ServerWebExchange exchange) {
+ handleMissingValue(name, parameter);
+ }
+
+ /**
+ * Invoked when a named value is required, but
+ * {@link #resolveName(String, MethodParameter, ServerWebExchange)} 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
+ */
+ protected void handleMissingValue(String name, MethodParameter parameter) {
+ String typeName = parameter.getNestedParameterType().getSimpleName();
+ throw new ServerWebInputException("Missing argument '" + name + "' for method " +
+ "parameter of type " + typeName, parameter);
+ }
+
+ /**
+ * 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.getSimpleName() +
+ " 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 model the model
+ * @param exchange the current exchange
+ */
+ @SuppressWarnings("UnusedParameters")
+ protected void handleResolvedValue(Object arg, String name, MethodParameter parameter,
+ ModelMap model, ServerWebExchange exchange) {
+ }
+
+
+ /**
+ * 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;
+
+ public NamedValueInfo(String name, boolean required, String defaultValue) {
+ this.name = name;
+ this.required = required;
+ this.defaultValue = defaultValue;
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java
new file mode 100644
index 0000000000..48f239f9cc
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.http.HttpCookie;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+
+/**
+ * Resolve method arguments annotated with {@code @CookieValue}.
+ *
+ * An {@code @CookieValue} is a named value that is resolved from a cookie.
+ * It has a required flag and a default value to fall back on when the cookie
+ * does not exist.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class CookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+
+ /**
+ * @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
+ */
+ public CookieValueMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory) {
+
+ super(conversionService, beanFactory);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(CookieValue.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ CookieValue annotation = parameter.getParameterAnnotation(CookieValue.class);
+ return new CookieValueNamedValueInfo(annotation);
+ }
+
+ @Override
+ protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
+ HttpCookie cookie = exchange.getRequest().getCookies().getFirst(name);
+ if (HttpCookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
+ return Mono.justOrEmpty(cookie);
+ }
+ else if (cookie != null) {
+ return Mono.justOrEmpty(cookie.getValue());
+ }
+ else {
+ return Mono.empty();
+ }
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) {
+ String type = parameter.getNestedParameterType().getSimpleName();
+ String reason = "Missing cookie '" + name + "' for method parameter of type " + type;
+ throw new ServerWebInputException(reason, parameter);
+ }
+
+
+ private static class CookieValueNamedValueInfo extends NamedValueInfo {
+
+ private CookieValueNamedValueInfo(CookieValue annotation) {
+ super(annotation.name(), annotation.required(), annotation.defaultValue());
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolver.java
new file mode 100644
index 0000000000..fb6f22d659
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolver.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Resolves method arguments annotated with {@code @Value}.
+ *
+ * An {@code @Value} does not have a name but gets resolved from the default
+ * value string, which may contain ${...} placeholder or Spring Expression
+ * Language #{...} expressions.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+
+ /**
+ * @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
+ */
+ public ExpressionValueMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory) {
+
+ super(conversionService, beanFactory);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(Value.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ Value annotation = parameter.getParameterAnnotation(Value.class);
+ return new ExpressionValueNamedValueInfo(annotation);
+ }
+
+ @Override
+ protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
+ // No name to resolve
+ return Mono.empty();
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) {
+ throw new UnsupportedOperationException("@Value is never required: " + parameter.getMethod());
+ }
+
+
+ private static class ExpressionValueNamedValueInfo extends NamedValueInfo {
+
+ private ExpressionValueNamedValueInfo(Value annotation) {
+ super("@Value", false, annotation.value());
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java
new file mode 100644
index 0000000000..9488bfa69c
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Resolver for {@link Map} method arguments also annotated with
+ * {@link PathVariable @PathVariable} where the annotation does not specify a
+ * path variable name. The resulting {@link Map} argument is a coyp of all URI
+ * template name-value pairs.
+ *
+ * @author Rossen Stoyanchev
+ * @see PathVariableMethodArgumentResolver
+ */
+public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
+ return (ann != null && (Map.class.isAssignableFrom(parameter.getParameterType()))
+ && !StringUtils.hasText(ann.value()));
+ }
+
+ /**
+ * Return a Map with all URI template variables or an empty map.
+ */
+ @Override
+ public Mono resolveArgument(MethodParameter parameter, ModelMap model,
+ ServerWebExchange exchange) {
+
+ String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
+ Optional value = exchange.getAttribute(name);
+ return (value.isPresent() ? Mono.just(value.get()) : Mono.just(Collections.emptyMap()));
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolver.java
new file mode 100644
index 0000000000..fd59d53e7c
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolver.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.Map;
+import java.util.Optional;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.server.ServerErrorException;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Resolves method arguments annotated with @{@link PathVariable}.
+ *
+ * An @{@link PathVariable} is a named value that gets resolved from a URI
+ * template variable. It is always required and does not have a default value
+ * to fall back on. See the base class
+ * {@link org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver}
+ * for more information on how named values are processed.
+ *
+ *
If the method parameter type is {@link Map}, the name specified in the
+ * annotation is used to resolve the URI variable String value. The value is
+ * then converted to a {@link Map} via type conversion, assuming a suitable
+ * {@link Converter}.
+ *
+ * @author Rossen Stoyanchev
+ * @see PathVariableMapMethodArgumentResolver
+ */
+public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+
+ public PathVariableMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory) {
+
+ super(conversionService, beanFactory);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ if (!parameter.hasParameterAnnotation(PathVariable.class)) {
+ return false;
+ }
+ if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
+ String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
+ return StringUtils.hasText(paramName);
+ }
+ return true;
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
+ return new PathVariableNamedValueInfo(annotation);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
+ String attributeName = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
+ Optional optional = exchange.getAttribute(attributeName);
+ Object value = null;
+ if (optional.isPresent()) {
+ value = ((Map) optional.get()).get(name);
+ }
+ return Mono.justOrEmpty(value);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) {
+ throw new ServerErrorException(name, parameter);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void handleResolvedValue(Object arg, String name, MethodParameter parameter,
+ ModelMap model, ServerWebExchange exchange) {
+
+ // TODO: View.PATH_VARIABLES ?
+ }
+
+
+ private static class PathVariableNamedValueInfo extends NamedValueInfo {
+
+ public PathVariableNamedValueInfo(PathVariable annotation) {
+ super(annotation.value(), true, ValueConstants.DEFAULT_NONE);
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java
new file mode 100644
index 0000000000..f14e9fdd35
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+
+/**
+ * Resolves method arguments annotated with an @{@link RequestAttribute}.
+ *
+ * @author Rossen Stoyanchev
+ * @see SessionAttributeMethodArgumentResolver
+ */
+public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+
+ public RequestAttributeMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory) {
+
+ super(conversionService, beanFactory);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(RequestAttribute.class);
+ }
+
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ RequestAttribute annot = parameter.getParameterAnnotation(RequestAttribute.class);
+ return new NamedValueInfo(annot.name(), annot.required(), ValueConstants.DEFAULT_NONE);
+ }
+
+ @Override
+ protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange){
+ return Mono.justOrEmpty(exchange.getAttribute(name));
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) {
+ String type = parameter.getNestedParameterType().getSimpleName();
+ String reason = "Missing request attribute '" + name + "' of type " + type;
+ throw new ServerWebInputException(reason, parameter);
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java
new file mode 100644
index 0000000000..4f3820e9c2
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.Map;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpHeaders;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Resolves {@link Map} method arguments annotated with {@code @RequestHeader}.
+ * For individual header values annotated with {@code @RequestHeader} see
+ * {@link RequestHeaderMethodArgumentResolver} instead.
+ *
+ * The created {@link Map} contains all request header name/value pairs.
+ * The method parameter type may be a {@link MultiValueMap} to receive all
+ * values for a header, not only the first one.
+ *
+ * @author Rossen Stoyanchev
+ * @see RequestHeaderMethodArgumentResolver
+ */
+public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return (parameter.hasParameterAnnotation(RequestHeader.class) &&
+ Map.class.isAssignableFrom(parameter.getParameterType()));
+ }
+
+ @Override
+ public Mono resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
+ HttpHeaders headers = exchange.getRequest().getHeaders();
+ if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
+ return Mono.just(headers);
+ }
+ else {
+ return Mono.just(headers.toSingleValueMap());
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java
new file mode 100644
index 0000000000..4f3cd089c5
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.List;
+import java.util.Map;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+
+/**
+ * Resolves method arguments annotated with {@code @RequestHeader} except for
+ * {@link Map} arguments. See {@link RequestHeaderMapMethodArgumentResolver} for
+ * details on {@link Map} arguments annotated with {@code @RequestHeader}.
+ *
+ * An {@code @RequestHeader} is a named value resolved from a request header.
+ * It has a required flag and a default value to fall back on when the request
+ * header does not exist.
+ *
+ *
A {@link ConversionService} is invoked to apply type conversion to resolved
+ * request header values that don't yet match the method parameter type.
+ *
+ * @author Rossen Stoyanchev
+ * @see RequestHeaderMapMethodArgumentResolver
+ */
+public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ /**
+ * @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 have expressions
+ */
+ public RequestHeaderMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory) {
+
+ super(conversionService, beanFactory);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return (parameter.hasParameterAnnotation(RequestHeader.class) &&
+ !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ RequestHeader annotation = parameter.getParameterAnnotation(RequestHeader.class);
+ return new RequestHeaderNamedValueInfo(annotation);
+ }
+
+ @Override
+ protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
+ List headerValues = exchange.getRequest().getHeaders().get(name);
+ Object result = null;
+ if (headerValues != null) {
+ result = (headerValues.size() == 1 ? headerValues.get(0) : headerValues);
+ }
+ return Mono.justOrEmpty(result);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) {
+ String type = parameter.getNestedParameterType().getSimpleName();
+ throw new ServerWebInputException("Missing request header '" + name +
+ "' for method parameter of type " + type);
+ }
+
+
+ private static class RequestHeaderNamedValueInfo extends NamedValueInfo {
+
+ private RequestHeaderNamedValueInfo(RequestHeader annotation) {
+ super(annotation.name(), annotation.required(), annotation.defaultValue());
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java
index d9d2527e62..b6c0ec6848 100644
--- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java
@@ -28,7 +28,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.codec.support.ByteBufferDecoder;
import org.springframework.core.codec.support.ByteBufferEncoder;
import org.springframework.core.codec.support.JacksonJsonDecoder;
@@ -57,7 +61,7 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Rossen Stoyanchev
*/
-public class RequestMappingHandlerAdapter implements HandlerAdapter, InitializingBean {
+public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactoryAware, InitializingBean {
private static Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
@@ -69,6 +73,9 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
private final Map, ExceptionHandlerMethodResolver> exceptionHandlerCache =
new ConcurrentHashMap<>(64);
+ private ConfigurableBeanFactory beanFactory;
+
+
/**
* Configure the complete list of supported argument types thus overriding
@@ -94,24 +101,52 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
return this.conversionService;
}
+ /**
+ * A {@link ConfigurableBeanFactory} is expected for resolving expressions
+ * in method argument default values.
+ */
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.beanFactory = (ConfigurableBeanFactory) beanFactory;
+ }
+ }
+
+ public ConfigurableBeanFactory getBeanFactory() {
+ return beanFactory;
+ }
+
+
@Override
public void afterPropertiesSet() throws Exception {
if (ObjectUtils.isEmpty(this.argumentResolvers)) {
- List> messageConverters = Arrays.asList(
- new CodecHttpMessageConverter(new ByteBufferEncoder(),
- new ByteBufferDecoder()),
- new CodecHttpMessageConverter(new StringEncoder(),
- new StringDecoder()),
- new CodecHttpMessageConverter(new Jaxb2Encoder(),
- new Jaxb2Decoder()),
+
+ List> converters = Arrays.asList(
+ new CodecHttpMessageConverter(new ByteBufferEncoder(), new ByteBufferDecoder()),
+ new CodecHttpMessageConverter(new StringEncoder(), new StringDecoder()),
+ new CodecHttpMessageConverter(new Jaxb2Encoder(), new Jaxb2Decoder()),
new CodecHttpMessageConverter(new JacksonJsonEncoder(),
new JacksonJsonDecoder(new JsonObjectDecoder())));
+ // Annotation-based argument resolution
+ ConversionService cs = getConversionService();
+ this.argumentResolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), false));
+ this.argumentResolvers.add(new RequestParamMapMethodArgumentResolver());
+ this.argumentResolvers.add(new PathVariableMethodArgumentResolver(cs, getBeanFactory()));
+ this.argumentResolvers.add(new PathVariableMapMethodArgumentResolver());
+ this.argumentResolvers.add(new RequestBodyArgumentResolver(converters, cs));
+ this.argumentResolvers.add(new RequestHeaderMethodArgumentResolver(cs, getBeanFactory()));
+ this.argumentResolvers.add(new RequestHeaderMapMethodArgumentResolver());
+ this.argumentResolvers.add(new CookieValueMethodArgumentResolver(cs, getBeanFactory()));
+ this.argumentResolvers.add(new ExpressionValueMethodArgumentResolver(cs, getBeanFactory()));
+ this.argumentResolvers.add(new SessionAttributeMethodArgumentResolver(cs, getBeanFactory()));
+ this.argumentResolvers.add(new RequestAttributeMethodArgumentResolver(cs , getBeanFactory()));
- this.argumentResolvers.add(new RequestParamArgumentResolver());
- this.argumentResolvers.add(new RequestBodyArgumentResolver(messageConverters,
- this.conversionService));
+ // Type-based argument resolution
this.argumentResolvers.add(new ModelArgumentResolver());
+
+ // Catch-all
+ this.argumentResolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), true));
}
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamArgumentResolver.java
deleted file mode 100644
index b518e3f628..0000000000
--- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamArgumentResolver.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2002-2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.web.reactive.result.method.annotation;
-
-import reactor.core.publisher.Mono;
-
-import org.springframework.core.MethodParameter;
-import org.springframework.ui.ModelMap;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
-import org.springframework.web.server.ServerWebExchange;
-import org.springframework.web.util.UriComponents;
-import org.springframework.web.util.UriComponentsBuilder;
-
-
-/**
- * Support {@code @RequestParam} but for query params only.
- *
- * @author Rossen Stoyanchev
- */
-public class RequestParamArgumentResolver implements HandlerMethodArgumentResolver {
-
-
- @Override
- public boolean supportsParameter(MethodParameter parameter) {
- return parameter.hasParameterAnnotation(RequestParam.class);
- }
-
-
- @Override
- public Mono resolveArgument(MethodParameter param, ModelMap model, ServerWebExchange exchange) {
- RequestParam annotation = param.getParameterAnnotation(RequestParam.class);
- String name = (annotation.value().length() != 0 ? annotation.value() : param.getParameterName());
- UriComponents uriComponents = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build();
- return Mono.justOrEmpty(uriComponents.getQueryParams().getFirst(name));
- }
-
-}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java
new file mode 100644
index 0000000000..50df6d3c4a
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.Map;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Resolver for {@link Map} method arguments annotated with
+ * {@link RequestParam @RequestParam} where the annotation does not specify a
+ * request parameter name. See {@link RequestParamMethodArgumentResolver} for
+ * resolving {@link Map} method arguments with a request parameter name.
+ *
+ * The created {@link Map} contains all request parameter name-value pairs.
+ * If the method parameter type is {@link MultiValueMap} instead, the created
+ * map contains all request parameters and all there values for cases where
+ * request parameters have multiple values.
+ *
+ * @author Rossen Stoyanchev
+ * @see RequestParamMethodArgumentResolver
+ */
+public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
+ if (requestParam != null) {
+ if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ return !StringUtils.hasText(requestParam.name());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Mono resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
+ Class> paramType = parameter.getParameterType();
+ MultiValueMap queryParams = exchange.getRequest().getQueryParams();
+ if (MultiValueMap.class.isAssignableFrom(paramType)) {
+ return Mono.just(queryParams);
+ }
+ else {
+ return Mono.just(queryParams.toSingleValueMap());
+ }
+ }
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java
new file mode 100644
index 0000000000..b55bb3c1a4
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.List;
+import java.util.Map;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+
+/**
+ * Resolver for method arguments annotated with @{@link RequestParam}.
+ *
+ * This resolver can also be created in default resolution mode in which
+ * simple types (int, long, etc.) not annotated with @{@link RequestParam} are
+ * also treated as request parameters with the parameter name derived from the
+ * argument name.
+ *
+ *
If the method parameter type is {@link Map}, the name specified in the
+ * annotation is used to resolve the request parameter String value. The value is
+ * then converted to a {@link Map} via type conversion assuming a suitable
+ * {@link Converter} has been registered. Or if a request parameter name is not
+ * specified the {@link RequestParamMapMethodArgumentResolver} is used instead
+ * to provide access to all request parameters in the form of a map.
+ *
+ * @author Rossen Stoyanchev
+ * @see RequestParamMapMethodArgumentResolver
+ */
+public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ private final boolean useDefaultResolution;
+
+
+ /**
+ * @param conversionService for type conversion (to be replaced with WebDataBinder)
+ * @param beanFactory a bean factory used for resolving ${...} placeholder
+ * and #{...} SpEL expressions in default values, or {@code null} if default
+ * values are not expected to contain expressions
+ * @param useDefaultResolution in default resolution mode a method argument
+ * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty},
+ * is treated as a request parameter even if it isn't annotated, the
+ * request parameter name is derived from the method parameter name.
+ */
+ public RequestParamMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
+
+ super(conversionService, beanFactory);
+ this.useDefaultResolution = useDefaultResolution;
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ if (parameter.hasParameterAnnotation(RequestParam.class)) {
+ if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
+ String paramName = parameter.getParameterAnnotation(RequestParam.class).name();
+ return StringUtils.hasText(paramName);
+ }
+ else {
+ return true;
+ }
+ }
+ return (this.useDefaultResolution && BeanUtils.isSimpleProperty(parameter.getNestedParameterType()));
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
+ return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
+ }
+
+ @Override
+ protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
+ List paramValues = exchange.getRequest().getQueryParams().get(name);
+ Object result = null;
+ if (paramValues != null) {
+ result = (paramValues.size() == 1 ? paramValues.get(0) : paramValues);
+ }
+ return Mono.justOrEmpty(result);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter, ServerWebExchange exchange) {
+ String type = parameter.getNestedParameterType().getSimpleName();
+ String reason = "Required " + type + " parameter '" + name + "' is not present";
+ throw new ServerWebInputException(reason, parameter);
+ }
+
+
+ private static class RequestParamNamedValueInfo extends NamedValueInfo {
+
+ public RequestParamNamedValueInfo() {
+ super("", false, ValueConstants.DEFAULT_NONE);
+ }
+
+ public RequestParamNamedValueInfo(RequestParam annotation) {
+ super(annotation.name(), annotation.required(), annotation.defaultValue());
+ }
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java
new file mode 100644
index 0000000000..b6eded3b22
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.util.Optional;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.web.bind.annotation.SessionAttribute;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+
+/**
+ * Resolves method arguments annotated with an @{@link SessionAttribute}.
+ *
+ * @author Rossen Stoyanchev
+ * @see RequestAttributeMethodArgumentResolver
+ */
+public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+
+ public SessionAttributeMethodArgumentResolver(ConversionService conversionService,
+ ConfigurableBeanFactory beanFactory) {
+
+ super(conversionService, beanFactory);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(SessionAttribute.class);
+ }
+
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ SessionAttribute annot = parameter.getParameterAnnotation(SessionAttribute.class);
+ return new NamedValueInfo(annot.name(), annot.required(), ValueConstants.DEFAULT_NONE);
+ }
+
+ @Override
+ protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange){
+ return exchange.getSession().map(session -> session.getAttribute(name))
+ .where(Optional::isPresent).map(Optional::get);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) {
+ String type = parameter.getNestedParameterType().getSimpleName();
+ String reason = "Missing session attribute '" + name + "' of type " + type;
+ throw new ServerWebInputException(reason, parameter);
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/server/ServerErrorException.java b/spring-web-reactive/src/main/java/org/springframework/web/server/ServerErrorException.java
new file mode 100644
index 0000000000..9289c3c459
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/server/ServerErrorException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2016 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.web.server;
+
+import java.util.Optional;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpStatus;
+
+/**
+ * Exception for errors that fit response status 500 (bad request) for use in
+ * Spring Web applications. The exception provides additional fields (e.g.
+ * an optional {@link MethodParameter} if related to the error).
+ *
+ * @author Rossen Stoyanchev
+ */
+public class ServerErrorException extends ResponseStatusException {
+
+ private final MethodParameter parameter;
+
+
+ /**
+ * Constructor with an explanation only.
+ */
+ public ServerErrorException(String reason) {
+ this(reason, null);
+ }
+
+ /**
+ * Constructor for a 500 error linked to a specific {@code MethodParameter}.
+ */
+ public ServerErrorException(String reason, MethodParameter parameter) {
+ this(reason, parameter, null);
+ }
+
+ /**
+ * Constructor for a 500 error with a root cause.
+ */
+ public ServerErrorException(String reason, MethodParameter parameter, Throwable cause) {
+ super(HttpStatus.INTERNAL_SERVER_ERROR, reason, cause);
+ this.parameter = parameter;
+ }
+
+
+ /**
+ * Return the {@code MethodParameter} associated with this error, if any.
+ */
+ public Optional getMethodParameter() {
+ return Optional.ofNullable(this.parameter);
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java
index 3d9ae0bbef..939c67e217 100644
--- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java
@@ -28,14 +28,16 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.Signal;
import reactor.core.util.SignalKind;
+import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
+import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerResult;
-import org.springframework.web.reactive.result.method.annotation.RequestParamArgumentResolver;
+import org.springframework.web.reactive.result.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.WebSessionManager;
@@ -82,8 +84,10 @@ public class InvocableHandlerMethodTests {
@Test
public void resolveArgToZeroValues() throws Exception {
when(this.request.getURI()).thenReturn(new URI("http://localhost:8080/path"));
+ when(this.request.getQueryParams()).thenReturn(new LinkedMultiValueMap<>());
InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class);
- hm.setHandlerMethodArgumentResolvers(Collections.singletonList(new RequestParamArgumentResolver()));
+ hm.setHandlerMethodArgumentResolvers(Collections.singletonList(
+ new RequestParamMethodArgumentResolver(new GenericConversionService(), null, false)));
Mono mono = hm.invokeForRequest(this.exchange, this.model);
HandlerResult value = mono.get();
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestAttributesArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestAttributesArgumentResolverTests.java
new file mode 100644
index 0000000000..ae1b4b9f25
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestAttributesArgumentResolverTests.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.core.test.TestSubscriber;
+
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.SessionAttribute;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+import org.springframework.web.server.WebSession;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base class for {@code @RequestAttribute} and {@code @SessionAttribute} method
+ * argument resolution tests.
+ *
+ * @author Rossen Stoyanchev
+ */
+public abstract class AbstractRequestAttributesArgumentResolverTests {
+
+ private HandlerMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private Method handleMethod;
+
+
+ @Before @SuppressWarnings("ConfusingArgumentToVarargsMethod")
+ public void setUp() throws Exception {
+ this.resolver = createResolver();
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ WebSession webSession = mock(WebSession.class);
+ when(sessionManager.getSession(this.exchange)).thenReturn(Mono.just(webSession));
+
+ this.handleMethod = ReflectionUtils.findMethod(getClass(), getHandleMethodName(), (Class>[]) null);
+ }
+
+
+ protected abstract HandlerMethodArgumentResolver createResolver();
+
+ protected abstract String getHandleMethodName();
+
+
+ @Test
+ public void supportsParameter() throws Exception {
+ assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
+ assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 4)));
+ }
+
+ @Test
+ public void resolve() throws Exception {
+ MethodParameter param = initMethodParameter(0);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ TestSubscriber subscriber = new TestSubscriber<>();
+ mono.subscribeWith(subscriber);
+ subscriber.assertError(ServerWebInputException.class);
+
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("foo", foo);
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveWithName() throws Exception {
+ MethodParameter param = initMethodParameter(1);
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("specialFoo", foo);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveNotRequired() throws Exception {
+ MethodParameter param = initMethodParameter(2);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertNull(mono.get());
+
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("foo", foo);
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveOptional() throws Exception {
+ MethodParameter param = initMethodParameter(3);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertNotNull(mono.get());
+ assertEquals(Optional.class, mono.get().getClass());
+ assertFalse(((Optional) mono.get()).isPresent());
+
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("foo", foo);
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+
+ assertNotNull(mono.get());
+ assertEquals(Optional.class, mono.get().getClass());
+ Optional optional = (Optional) mono.get();
+ assertTrue(optional.isPresent());
+ assertSame(foo, optional.get());
+ }
+
+
+ private MethodParameter initMethodParameter(int parameterIndex) {
+ MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
+ param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
+ GenericTypeResolver.resolveParameterType(param, this.resolver.getClass());
+ return param;
+ }
+
+
+ @SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
+ private void handleWithRequestAttribute(
+ @RequestAttribute Foo foo,
+ @RequestAttribute("specialFoo") Foo namedFoo,
+ @RequestAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @RequestAttribute(name="foo") Optional optionalFoo) {
+ }
+
+ @SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
+ private void handleWithSessionAttribute(
+ @SessionAttribute Foo foo,
+ @SessionAttribute("specialFoo") Foo namedFoo,
+ @SessionAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @SessionAttribute(name="foo") Optional optionalFoo) {
+ }
+
+ private static class Foo {
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..e31b056e62
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolverTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.core.test.TestSubscriber;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.http.HttpCookie;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Test fixture with {@link CookieValueMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class CookieValueMethodArgumentResolverTests {
+
+ private CookieValueMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private MethodParameter cookieParameter;
+ private MethodParameter cookieStringParameter;
+ private MethodParameter stringParameter;
+
+
+ @Before
+ public void setUp() throws Exception {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.refresh();
+ ConversionService cs = new DefaultConversionService();
+ this.resolver = new CookieValueMethodArgumentResolver(cs, context.getBeanFactory());
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ Method method = getClass().getMethod("params", HttpCookie.class, String.class, String.class);
+ this.cookieParameter = new SynthesizingMethodParameter(method, 0);
+ this.cookieStringParameter = new SynthesizingMethodParameter(method, 1);
+ this.stringParameter = new SynthesizingMethodParameter(method, 2);
+ }
+
+
+ @Test
+ public void supportsParameter() {
+ assertTrue(this.resolver.supportsParameter(this.cookieParameter));
+ assertTrue(this.resolver.supportsParameter(this.cookieStringParameter));
+ assertFalse(this.resolver.supportsParameter(this.stringParameter));
+ }
+
+ @Test
+ public void resolveCookieArgument() {
+ HttpCookie expected = new HttpCookie("name", "foo");
+ this.exchange.getRequest().getCookies().add(expected.getName(), expected);
+
+ Mono mono = this.resolver.resolveArgument(this.cookieParameter, null, this.exchange);
+ assertEquals(expected, mono.get());
+ }
+
+ @Test
+ public void resolveCookieStringArgument() {
+ HttpCookie cookie = new HttpCookie("name", "foo");
+ this.exchange.getRequest().getCookies().add(cookie.getName(), cookie);
+
+ Mono mono = this.resolver.resolveArgument(this.cookieStringParameter, null, this.exchange);
+ assertEquals("Invalid result", cookie.getValue(), mono.get());
+ }
+
+ @Test
+ public void resolveCookieDefaultValue() {
+ Mono mono = this.resolver.resolveArgument(this.cookieStringParameter, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof String);
+ assertEquals("bar", result);
+ }
+
+ @Test
+ public void notFound() {
+ Mono mono = resolver.resolveArgument(this.cookieParameter, null, this.exchange);
+ TestSubscriber subscriber = new TestSubscriber<>();
+ mono.subscribeWith(subscriber);
+ subscriber.assertError(ServerWebInputException.class);
+ }
+
+
+ @SuppressWarnings("unused")
+ public void params(
+ @CookieValue("name") HttpCookie cookie,
+ @CookieValue(name = "name", defaultValue = "bar") String cookieString,
+ String stringParam) {
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..52ae4a2a11
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolverTests.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link ExpressionValueMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class ExpressionValueMethodArgumentResolverTests {
+
+ private ExpressionValueMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private MethodParameter paramSystemProperty;
+ private MethodParameter paramNotSupported;
+
+
+ @Before
+ public void setUp() throws Exception {
+ ConversionService conversionService = new GenericConversionService();
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.refresh();
+ this.resolver = new ExpressionValueMethodArgumentResolver(conversionService, context.getBeanFactory());
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ Method method = getClass().getMethod("params", int.class, String.class);
+ this.paramSystemProperty = new MethodParameter(method, 0);
+ this.paramNotSupported = new MethodParameter(method, 1);
+ }
+
+
+ @Test
+ public void supportsParameter() throws Exception {
+ assertTrue(this.resolver.supportsParameter(this.paramSystemProperty));
+ assertFalse(this.resolver.supportsParameter(this.paramNotSupported));
+ }
+
+ @Test
+ public void resolveSystemProperty() throws Exception {
+ System.setProperty("systemProperty", "22");
+ try {
+ Mono mono = this.resolver.resolveArgument(this.paramSystemProperty, null, this.exchange);
+ Object value = mono.get();
+ assertEquals(22, value);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+
+ }
+
+ // TODO: test with expression for ServerWebExchange
+
+
+ @SuppressWarnings("unused")
+ public void params(@Value("#{systemProperties.systemProperty}") int param1,
+ String notSupported) {
+ }
+
+}
\ No newline at end of file
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..851e77e804
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolverTests.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link PathVariableMapMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class PathVariableMapMethodArgumentResolverTests {
+
+ private PathVariableMapMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private MethodParameter paramMap;
+ private MethodParameter paramNamedMap;
+ private MethodParameter paramMapNoAnnot;
+
+
+ @Before
+ public void setUp() throws Exception {
+ this.resolver = new PathVariableMapMethodArgumentResolver();
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ Method method = getClass().getMethod("handle", Map.class, Map.class, Map.class);
+ this.paramMap = new MethodParameter(method, 0);
+ this.paramNamedMap = new MethodParameter(method, 1);
+ this.paramMapNoAnnot = new MethodParameter(method, 2);
+ }
+
+ @Test
+ public void supportsParameter() {
+ assertTrue(resolver.supportsParameter(paramMap));
+ assertFalse(resolver.supportsParameter(paramNamedMap));
+ assertFalse(resolver.supportsParameter(paramMapNoAnnot));
+ }
+
+ @Test
+ public void resolveArgument() throws Exception {
+ Map uriTemplateVars = new HashMap<>();
+ uriTemplateVars.put("name1", "value1");
+ uriTemplateVars.put("name2", "value2");
+ this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
+
+ Mono mono = this.resolver.resolveArgument(this.paramMap, new ModelMap(), this.exchange);
+ Object result = mono.get();
+
+ assertEquals(uriTemplateVars, result);
+ }
+
+ @Test
+ public void resolveArgumentNoUriVars() throws Exception {
+ Mono mono = this.resolver.resolveArgument(this.paramMap, new ModelMap(), this.exchange);
+ Object result = mono.get();
+
+ assertEquals(Collections.emptyMap(), result);
+ }
+
+
+ @SuppressWarnings("unused")
+ public void handle(
+ @PathVariable Map map,
+ @PathVariable(value = "name") Map namedMap,
+ Map mapWithoutAnnotat) {
+ }
+
+}
\ No newline at end of file
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..f79d713013
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolverTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.core.test.TestSubscriber;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.server.ServerErrorException;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link PathVariableMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class PathVariableMethodArgumentResolverTests {
+
+ private PathVariableMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private MethodParameter paramNamedString;
+ private MethodParameter paramString;
+
+
+ @Before
+ public void setUp() throws Exception {
+ ConversionService conversionService = new DefaultConversionService();
+ this.resolver = new PathVariableMethodArgumentResolver(conversionService, null);
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ Method method = getClass().getMethod("handle", String.class, String.class);
+ this.paramNamedString = new MethodParameter(method, 0);
+ this.paramString = new MethodParameter(method, 1);
+ }
+
+
+ @Test
+ public void supportsParameter() {
+ assertTrue(this.resolver.supportsParameter(this.paramNamedString));
+ assertFalse(this.resolver.supportsParameter(this.paramString));
+ }
+
+ @Test
+ public void resolveArgument() throws Exception {
+ Map uriTemplateVars = new HashMap<>();
+ uriTemplateVars.put("name", "value");
+ this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
+
+ Mono mono = this.resolver.resolveArgument(this.paramNamedString, new ModelMap(), this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof String);
+ assertEquals("value", result);
+ }
+
+ @Test
+ public void handleMissingValue() throws Exception {
+ Mono mono = this.resolver.resolveArgument(this.paramNamedString, new ModelMap(), this.exchange);
+ TestSubscriber subscriber = new TestSubscriber<>();
+ mono.subscribeWith(subscriber);
+ subscriber.assertError(ServerErrorException.class);
+ }
+
+ @SuppressWarnings("unused")
+ public void handle(@PathVariable(value = "name") String param1, String param2) {
+ }
+
+}
\ No newline at end of file
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..3d82d8e04e
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolverTests.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.core.test.TestSubscriber;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+
+/**
+ * Unit tests for {@link RequestAttributeMethodArgumentResolver}.
+ * @author Rossen Stoyanchev
+ */
+public class RequestAttributeMethodArgumentResolverTests {
+
+ private RequestAttributeMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private Method handleMethod;
+
+
+ @Before
+ @SuppressWarnings("ConfusingArgumentToVarargsMethod")
+ public void setUp() throws Exception {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.refresh();
+ ConversionService cs = new DefaultConversionService();
+ this.resolver = new RequestAttributeMethodArgumentResolver(cs, context.getBeanFactory());
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ this.handleMethod = ReflectionUtils.findMethod(getClass(), "handleWithRequestAttribute", (Class>[]) null);
+ }
+
+
+ @Test
+ public void supportsParameter() throws Exception {
+ assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
+ assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 4)));
+ }
+
+ @Test
+ public void resolve() throws Exception {
+ MethodParameter param = initMethodParameter(0);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ TestSubscriber subscriber = new TestSubscriber<>();
+ mono.subscribeWith(subscriber);
+ subscriber.assertError(ServerWebInputException.class);
+
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("foo", foo);
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveWithName() throws Exception {
+ MethodParameter param = initMethodParameter(1);
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("specialFoo", foo);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveNotRequired() throws Exception {
+ MethodParameter param = initMethodParameter(2);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertNull(mono.get());
+
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("foo", foo);
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveOptional() throws Exception {
+ MethodParameter param = initMethodParameter(3);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertNotNull(mono.get());
+ assertEquals(Optional.class, mono.get().getClass());
+ assertFalse(((Optional) mono.get()).isPresent());
+
+ Foo foo = new Foo();
+ this.exchange.getAttributes().put("foo", foo);
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+
+ assertNotNull(mono.get());
+ assertEquals(Optional.class, mono.get().getClass());
+ Optional optional = (Optional) mono.get();
+ assertTrue(optional.isPresent());
+ assertSame(foo, optional.get());
+ }
+
+
+ private MethodParameter initMethodParameter(int parameterIndex) {
+ MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
+ param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
+ GenericTypeResolver.resolveParameterType(param, this.resolver.getClass());
+ return param;
+ }
+
+
+ @SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
+ private void handleWithRequestAttribute(
+ @RequestAttribute Foo foo,
+ @RequestAttribute("specialFoo") Foo namedFoo,
+ @RequestAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @RequestAttribute(name="foo") Optional optionalFoo) {
+ }
+
+ private static class Foo {
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..a2e48579af
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolverTests.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link RequestHeaderMapMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class RequestHeaderMapMethodArgumentResolverTests {
+
+ private RequestHeaderMapMethodArgumentResolver resolver;
+
+ private MethodParameter paramMap;
+ private MethodParameter paramMultiValueMap;
+ private MethodParameter paramHttpHeaders;
+ private MethodParameter paramUnsupported;
+
+ private ServerWebExchange exchange;
+
+
+ @Before
+ public void setUp() throws Exception {
+ resolver = new RequestHeaderMapMethodArgumentResolver();
+
+ Method method = getClass().getMethod("params", Map.class, MultiValueMap.class, HttpHeaders.class, Map.class);
+ paramMap = new SynthesizingMethodParameter(method, 0);
+ paramMultiValueMap = new SynthesizingMethodParameter(method, 1);
+ paramHttpHeaders = new SynthesizingMethodParameter(method, 2);
+ paramUnsupported = new SynthesizingMethodParameter(method, 3);
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+ }
+
+
+ @Test
+ public void supportsParameter() {
+ assertTrue("Map parameter not supported", resolver.supportsParameter(paramMap));
+ assertTrue("MultiValueMap parameter not supported", resolver.supportsParameter(paramMultiValueMap));
+ assertTrue("HttpHeaders parameter not supported", resolver.supportsParameter(paramHttpHeaders));
+ assertFalse("non-@RequestParam map supported", resolver.supportsParameter(paramUnsupported));
+ }
+
+ @Test
+ public void resolveMapArgument() throws Exception {
+ String name = "foo";
+ String value = "bar";
+ Map expected = Collections.singletonMap(name, value);
+ this.exchange.getRequest().getHeaders().add(name, value);
+
+ Mono mono = this.resolver.resolveArgument(paramMap, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof Map);
+ assertEquals("Invalid result", expected, result);
+ }
+
+ @Test
+ public void resolveMultiValueMapArgument() throws Exception {
+ String name = "foo";
+ String value1 = "bar";
+ String value2 = "baz";
+
+ this.exchange.getRequest().getHeaders().add(name, value1);
+ this.exchange.getRequest().getHeaders().add(name, value2);
+
+ MultiValueMap expected = new LinkedMultiValueMap<>(1);
+ expected.add(name, value1);
+ expected.add(name, value2);
+
+ Mono mono = this.resolver.resolveArgument(paramMultiValueMap, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof MultiValueMap);
+ assertEquals("Invalid result", expected, result);
+ }
+
+ @Test
+ public void resolveHttpHeadersArgument() throws Exception {
+ String name = "foo";
+ String value1 = "bar";
+ String value2 = "baz";
+
+ this.exchange.getRequest().getHeaders().add(name, value1);
+ this.exchange.getRequest().getHeaders().add(name, value2);
+
+ HttpHeaders expected = new HttpHeaders();
+ expected.add(name, value1);
+ expected.add(name, value2);
+
+ Mono mono = this.resolver.resolveArgument(paramHttpHeaders, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof HttpHeaders);
+ assertEquals("Invalid result", expected, result);
+ }
+
+
+ @SuppressWarnings("unused")
+ public void params(@RequestHeader Map, ?> param1,
+ @RequestHeader MultiValueMap, ?> param2,
+ @RequestHeader HttpHeaders param3,
+ Map,?> unsupported) {
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..d8dcfbcece
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolverTests.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.core.test.TestSubscriber;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.format.support.DefaultFormattingConversionService;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+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.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link RequestHeaderMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class RequestHeaderMethodArgumentResolverTests {
+
+ private RequestHeaderMethodArgumentResolver resolver;
+
+ private MethodParameter paramNamedDefaultValueStringHeader;
+ private MethodParameter paramNamedValueStringArray;
+ private MethodParameter paramSystemProperty;
+ private MethodParameter paramResolvedNameWithExpression;
+ private MethodParameter paramResolvedNameWithPlaceholder;
+ private MethodParameter paramNamedValueMap;
+ private MethodParameter paramDate;
+ private MethodParameter paramInstant;
+
+ private ServerWebExchange exchange;
+
+
+ @Before
+ public void setUp() throws Exception {
+ ConversionService conversionService = new DefaultFormattingConversionService();
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.refresh();
+ this.resolver = new RequestHeaderMethodArgumentResolver(conversionService, context.getBeanFactory());
+
+ @SuppressWarnings("ConfusingArgumentToVarargsMethod")
+ Method method = ReflectionUtils.findMethod(getClass(), "params", null);
+ this.paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 0);
+ this.paramNamedValueStringArray = new SynthesizingMethodParameter(method, 1);
+ this.paramSystemProperty = new SynthesizingMethodParameter(method, 2);
+ this.paramResolvedNameWithExpression = new SynthesizingMethodParameter(method, 3);
+ this.paramResolvedNameWithPlaceholder = new SynthesizingMethodParameter(method, 4);
+ this.paramNamedValueMap = new SynthesizingMethodParameter(method, 5);
+ this.paramDate = new SynthesizingMethodParameter(method, 6);
+ this.paramInstant = new SynthesizingMethodParameter(method, 7);
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+ }
+
+
+ @Test
+ public void supportsParameter() {
+ assertTrue("String parameter not supported", resolver.supportsParameter(paramNamedDefaultValueStringHeader));
+ assertTrue("String array parameter not supported", resolver.supportsParameter(paramNamedValueStringArray));
+ assertFalse("non-@RequestParam parameter supported", resolver.supportsParameter(paramNamedValueMap));
+ }
+
+ @Test
+ public void resolveStringArgument() throws Exception {
+ String expected = "foo";
+ this.exchange.getRequest().getHeaders().add("name", expected);
+
+ Mono mono = this.resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, this.exchange);
+ Object result = mono.get();
+ assertTrue(result instanceof String);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void resolveStringArrayArgument() throws Exception {
+ String[] expected = new String[] {"foo", "bar"};
+ this.exchange.getRequest().getHeaders().put("name", Arrays.asList(expected));
+
+ Mono mono = this.resolver.resolveArgument(paramNamedValueStringArray, null, this.exchange);
+ Object result = mono.get();
+ assertTrue(result instanceof String[]);
+ assertArrayEquals(expected, (String[]) result);
+ }
+
+ @Test
+ public void resolveDefaultValue() throws Exception {
+ Mono mono = this.resolver.resolveArgument(paramNamedDefaultValueStringHeader, null, this.exchange);
+ Object result = mono.get();
+ assertTrue(result instanceof String);
+ assertEquals("bar", result);
+ }
+
+ @Test
+ public void resolveDefaultValueFromSystemProperty() throws Exception {
+ System.setProperty("systemProperty", "bar");
+ try {
+ Mono mono = this.resolver.resolveArgument(paramSystemProperty, null, this.exchange);
+ Object result = mono.get();
+ assertTrue(result instanceof String);
+ assertEquals("bar", result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+ }
+
+ @Test
+ public void resolveNameFromSystemPropertyThroughExpression() throws Exception {
+ String expected = "foo";
+ this.exchange.getRequest().getHeaders().add("bar", expected);
+
+ System.setProperty("systemProperty", "bar");
+ try {
+ Mono mono = this.resolver.resolveArgument(paramResolvedNameWithExpression, null, this.exchange);
+ Object result = mono.get();
+ assertTrue(result instanceof String);
+ assertEquals(expected, result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+ }
+
+ @Test
+ public void resolveNameFromSystemPropertyThroughPlaceholder() throws Exception {
+ String expected = "foo";
+ this.exchange.getRequest().getHeaders().add("bar", expected);
+
+ System.setProperty("systemProperty", "bar");
+ try {
+ Mono mono = this.resolver.resolveArgument(paramResolvedNameWithPlaceholder, null, this.exchange);
+ Object result = mono.get();
+ assertTrue(result instanceof String);
+ assertEquals(expected, result);
+ }
+ finally {
+ System.clearProperty("systemProperty");
+ }
+ }
+
+ @Test
+ public void notFound() throws Exception {
+ Mono mono = resolver.resolveArgument(paramNamedValueStringArray, null, this.exchange);
+ TestSubscriber subscriber = new TestSubscriber<>();
+ mono.subscribeWith(subscriber);
+ subscriber.assertError(ServerWebInputException.class);
+ }
+
+ @Test
+ public void dateConversion() throws Exception {
+ String rfc1123val = "Thu, 21 Apr 2016 17:11:08 +0100";
+ this.exchange.getRequest().getHeaders().add("name", rfc1123val);
+
+ Mono mono = this.resolver.resolveArgument(paramDate, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof Date);
+ assertEquals(new Date(rfc1123val), result);
+ }
+
+ @Test
+ public void instantConversion() throws Exception {
+ String rfc1123val = "Thu, 21 Apr 2016 17:11:08 +0100";
+ this.exchange.getRequest().getHeaders().add("name", rfc1123val);
+
+ Mono mono = this.resolver.resolveArgument(paramInstant, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof Instant);
+ assertEquals(Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(rfc1123val)), result);
+ }
+
+
+ @SuppressWarnings("unused")
+ public void params(
+ @RequestHeader(name = "name", defaultValue = "bar") String param1,
+ @RequestHeader("name") String[] param2,
+ @RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
+ @RequestHeader("#{systemProperties.systemProperty}") String param4,
+ @RequestHeader("${systemProperty}") String param5,
+ @RequestHeader("name") Map, ?> unsupported,
+ @RequestHeader("name") Date dateParam,
+ @RequestHeader("name") Instant instantParam) {
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..c33daa06ef
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolverTests.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link RequestParamMapMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class RequestParamMapMethodArgumentResolverTests {
+
+ private RequestParamMapMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private MethodParameter paramMap;
+ private MethodParameter paramMultiValueMap;
+ private MethodParameter paramNamedMap;
+ private MethodParameter paramMapWithoutAnnot;
+
+
+
+ @Before
+ public void setUp() throws Exception {
+ this.resolver = new RequestParamMapMethodArgumentResolver();
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ Method method = getClass().getMethod("params", Map.class, MultiValueMap.class, Map.class, Map.class);
+ this.paramMap = new SynthesizingMethodParameter(method, 0);
+ this.paramMultiValueMap = new SynthesizingMethodParameter(method, 1);
+ this.paramNamedMap = new SynthesizingMethodParameter(method, 2);
+ this.paramMapWithoutAnnot = new SynthesizingMethodParameter(method, 3);
+ }
+
+
+ @Test
+ public void supportsParameter() {
+ assertTrue(this.resolver.supportsParameter(this.paramMap));
+ assertTrue(this.resolver.supportsParameter(this.paramMultiValueMap));
+ assertFalse(this.resolver.supportsParameter(this.paramNamedMap));
+ assertFalse(this.resolver.supportsParameter(this.paramMapWithoutAnnot));
+ }
+
+ @Test
+ public void resolveMapArgument() throws Exception {
+ String name = "foo";
+ String value = "bar";
+ this.exchange.getRequest().getQueryParams().set(name, value);
+ Map expected = Collections.singletonMap(name, value);
+
+ Mono mono = resolver.resolveArgument(paramMap, null, exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof Map);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void resolveMultiValueMapArgument() throws Exception {
+ String name = "foo";
+ String value1 = "bar";
+ String value2 = "baz";
+ this.exchange.getRequest().getQueryParams().put(name, Arrays.asList(value1, value2));
+
+ MultiValueMap expected = new LinkedMultiValueMap<>(1);
+ expected.add(name, value1);
+ expected.add(name, value2);
+
+ Mono mono = this.resolver.resolveArgument(this.paramMultiValueMap, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof MultiValueMap);
+ assertEquals(expected, result);
+ }
+
+
+ @SuppressWarnings("unused")
+ public void params(@RequestParam Map, ?> param1,
+ @RequestParam MultiValueMap, ?> param2,
+ @RequestParam("name") Map, ?> param3,
+ Map, ?> param4) {
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..4e6979dfcc
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.core.test.TestSubscriber;
+
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Unit tests for {@link RequestParamMethodArgumentResolver}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class RequestParamMethodArgumentResolverTests {
+
+ private RequestParamMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private MethodParameter paramNamedDefaultValueString;
+ private MethodParameter paramNamedStringArray;
+ private MethodParameter paramNamedMap;
+ private MethodParameter paramMap;
+ private MethodParameter paramStringNotAnnot;
+ private MethodParameter paramRequired;
+ private MethodParameter paramNotRequired;
+ private MethodParameter paramOptional;
+
+
+ @Before @SuppressWarnings("ConfusingArgumentToVarargsMethod")
+ public void setUp() throws Exception {
+ ConversionService conversionService = new DefaultConversionService();
+ this.resolver = new RequestParamMethodArgumentResolver(conversionService, null, true);
+
+ ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
+ Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class>[]) null);
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ this.paramNamedDefaultValueString = new SynthesizingMethodParameter(method, 0);
+ this.paramNamedStringArray = new SynthesizingMethodParameter(method, 1);
+ this.paramNamedMap = new SynthesizingMethodParameter(method, 2);
+ this.paramMap = new SynthesizingMethodParameter(method, 3);
+ this.paramStringNotAnnot = new SynthesizingMethodParameter(method, 4);
+ this.paramStringNotAnnot.initParameterNameDiscovery(paramNameDiscoverer);
+ this.paramRequired = new SynthesizingMethodParameter(method, 5);
+ this.paramNotRequired = new SynthesizingMethodParameter(method, 6);
+ this.paramOptional = new SynthesizingMethodParameter(method, 7);
+ }
+
+
+ @Test
+ public void supportsParameter() {
+ this.resolver = new RequestParamMethodArgumentResolver(new GenericConversionService(), null, true);
+ assertTrue(this.resolver.supportsParameter(this.paramNamedDefaultValueString));
+ assertTrue(this.resolver.supportsParameter(this.paramNamedStringArray));
+ assertTrue(this.resolver.supportsParameter(this.paramNamedMap));
+ assertFalse(this.resolver.supportsParameter(this.paramMap));
+ assertTrue(this.resolver.supportsParameter(this.paramStringNotAnnot));
+ assertTrue(this.resolver.supportsParameter(this.paramRequired));
+ assertTrue(this.resolver.supportsParameter(this.paramNotRequired));
+ assertTrue(this.resolver.supportsParameter(this.paramOptional));
+
+ this.resolver = new RequestParamMethodArgumentResolver(new GenericConversionService(), null, false);
+ assertFalse(this.resolver.supportsParameter(this.paramStringNotAnnot));
+ }
+
+ @Test
+ public void resolveString() throws Exception {
+ String expected = "foo";
+ this.exchange.getRequest().getQueryParams().set("name", expected);
+
+ Mono mono = this.resolver.resolveArgument(this.paramNamedDefaultValueString, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof String);
+ assertEquals("Invalid result", expected, result);
+ }
+
+ @Test
+ public void resolveStringArray() throws Exception {
+ String[] expected = {"foo", "bar"};
+ this.exchange.getRequest().getQueryParams().put("name", Arrays.asList(expected));
+
+ Mono mono = this.resolver.resolveArgument(this.paramNamedStringArray, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof String[]);
+ assertArrayEquals(expected, (String[]) result);
+ }
+
+ @Test
+ public void resolveDefaultValue() throws Exception {
+ Mono mono = this.resolver.resolveArgument(paramNamedDefaultValueString, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof String);
+ assertEquals("Invalid result", "bar", result);
+ }
+
+ @Test
+ public void missingRequestParam() throws Exception {
+ Mono mono = this.resolver.resolveArgument(paramNamedStringArray, null, this.exchange);
+ TestSubscriber subscriber = new TestSubscriber<>();
+ mono.subscribeWith(subscriber);
+ subscriber.assertError(ServerWebInputException.class);
+ }
+
+ @Test
+ public void resolveSimpleTypeParam() throws Exception {
+ this.exchange.getRequest().getQueryParams().set("stringNotAnnot", "plainValue");
+ Mono mono = this.resolver.resolveArgument(paramStringNotAnnot, null, this.exchange);
+ Object result = mono.get();
+
+ assertTrue(result instanceof String);
+ assertEquals("plainValue", result);
+ }
+
+ @Test // SPR-8561
+ public void resolveSimpleTypeParamToNull() throws Exception {
+ Mono mono = this.resolver.resolveArgument(paramStringNotAnnot, null, this.exchange);
+ Object result = mono.get();
+
+ assertNull(result);
+ }
+
+ @Test // SPR-10180
+ public void resolveEmptyValueToDefault() throws Exception {
+ this.exchange.getRequest().getQueryParams().set("name", "");
+ Mono mono = this.resolver.resolveArgument(paramNamedDefaultValueString, null, this.exchange);
+ Object result = mono.get();
+
+ assertEquals("bar", result);
+ }
+
+ @Test
+ public void resolveEmptyValueWithoutDefault() throws Exception {
+ this.exchange.getRequest().getQueryParams().set("stringNotAnnot", "");
+ Mono mono = this.resolver.resolveArgument(paramStringNotAnnot, null, this.exchange);
+ Object result = mono.get();
+
+ assertEquals("", result);
+ }
+
+ @Test
+ public void resolveEmptyValueRequiredWithoutDefault() throws Exception {
+ this.exchange.getRequest().getQueryParams().set("name", "");
+ Mono mono = this.resolver.resolveArgument(paramRequired, null, this.exchange);
+ Object result = mono.get();
+
+ assertEquals("", result);
+ }
+
+ @Test
+ public void resolveOptionalParamValue() throws Exception {
+ Mono mono = this.resolver.resolveArgument(paramOptional, null, this.exchange);
+ Object result = mono.get();
+
+ assertEquals(Optional.empty(), result);
+
+ this.exchange.getRequest().getQueryParams().set("name", "123");
+ mono = resolver.resolveArgument(paramOptional, null, this.exchange);
+ result = mono.get();
+
+ assertEquals(Optional.class, result.getClass());
+ Optional> value = (Optional>) result;
+ assertTrue(value.isPresent());
+ assertEquals(123, value.get());
+ }
+
+
+ @SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
+ public void handle(
+ @RequestParam(name = "name", defaultValue = "bar") String param1,
+ @RequestParam("name") String[] param2,
+ @RequestParam("name") Map, ?> param3,
+ @RequestParam Map, ?> param4,
+ String stringNotAnnot,
+ @RequestParam("name") String paramRequired,
+ @RequestParam(name = "name", required = false) String paramNotRequired,
+ @RequestParam("name") Optional paramOptional) {
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolverTests.java
new file mode 100644
index 0000000000..1bdbc9ce4a
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolverTests.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2002-2016 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.web.reactive.result.method.annotation;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+import reactor.core.test.TestSubscriber;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.MockServerHttpRequest;
+import org.springframework.http.server.reactive.MockServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.bind.annotation.SessionAttribute;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebInputException;
+import org.springframework.web.server.WebSession;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.server.session.WebSessionManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link SessionAttributeMethodArgumentResolver}.
+ * @author Rossen Stoyanchev
+ */
+public class SessionAttributeMethodArgumentResolverTests {
+
+ private SessionAttributeMethodArgumentResolver resolver;
+
+ private ServerWebExchange exchange;
+
+ private WebSession session;
+
+ private Method handleMethod;
+
+
+ @Before
+ @SuppressWarnings("ConfusingArgumentToVarargsMethod")
+ public void setUp() throws Exception {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.refresh();
+ ConversionService cs = new DefaultConversionService();
+ this.resolver = new SessionAttributeMethodArgumentResolver(cs, context.getBeanFactory());
+
+ ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/"));
+ WebSessionManager sessionManager = mock(WebSessionManager.class);
+ this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
+
+ this.session = mock(WebSession.class);
+ when(sessionManager.getSession(this.exchange)).thenReturn(Mono.just(this.session));
+ when(this.session.getAttribute(any())).thenReturn(Optional.empty());
+
+ this.handleMethod = ReflectionUtils.findMethod(getClass(), "handleWithSessionAttribute", (Class>[]) null);
+ }
+
+ @Test
+ public void supportsParameter() throws Exception {
+ assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
+ assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 4)));
+ }
+
+ @Test
+ public void resolve() throws Exception {
+ MethodParameter param = initMethodParameter(0);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ TestSubscriber subscriber = new TestSubscriber<>();
+ mono.subscribeWith(subscriber);
+ subscriber.assertError(ServerWebInputException.class);
+
+ Foo foo = new Foo();
+ when(this.session.getAttribute("foo")).thenReturn(Optional.of(foo));
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveWithName() throws Exception {
+ MethodParameter param = initMethodParameter(1);
+ Foo foo = new Foo();
+ when(this.session.getAttribute("specialFoo")).thenReturn(Optional.of(foo));
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveNotRequired() throws Exception {
+ MethodParameter param = initMethodParameter(2);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertNull(mono.get());
+
+ Foo foo = new Foo();
+ when(this.session.getAttribute("foo")).thenReturn(Optional.of(foo));
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertSame(foo, mono.get());
+ }
+
+ @Test
+ public void resolveOptional() throws Exception {
+ MethodParameter param = initMethodParameter(3);
+ Mono mono = this.resolver.resolveArgument(param, null, this.exchange);
+ assertNotNull(mono.get());
+ assertEquals(Optional.class, mono.get().getClass());
+ assertFalse(((Optional) mono.get()).isPresent());
+
+ Foo foo = new Foo();
+ when(this.session.getAttribute("foo")).thenReturn(Optional.of(foo));
+ mono = this.resolver.resolveArgument(param, null, this.exchange);
+
+ assertNotNull(mono.get());
+ assertEquals(Optional.class, mono.get().getClass());
+ Optional optional = (Optional) mono.get();
+ assertTrue(optional.isPresent());
+ assertSame(foo, optional.get());
+ }
+
+
+ private MethodParameter initMethodParameter(int parameterIndex) {
+ MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
+ param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
+ GenericTypeResolver.resolveParameterType(param, this.resolver.getClass());
+ return param;
+ }
+
+
+ @SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
+ private void handleWithSessionAttribute(
+ @SessionAttribute Foo foo,
+ @SessionAttribute("specialFoo") Foo namedFoo,
+ @SessionAttribute(name="foo", required = false) Foo notRequiredFoo,
+ @SessionAttribute(name="foo") Optional optionalFoo) {
+ }
+
+ private static class Foo {
+ }
+
+}