Add support for more annotated method arguments
- @PathVariable - @RequestHeader - @RequestParam - @CookieValue - @Value - @RequestAttribute - @SessionAttribute
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
* <p>Subclasses define how to do the following:
|
||||
* <ul>
|
||||
* <li>Obtain named value information for a method parameter
|
||||
* <li>Resolve names into argument values
|
||||
* <li>Handle missing argument values when argument values are required
|
||||
* <li>Optionally handle a resolved value
|
||||
* </ul>
|
||||
* <p>A default value string can contain ${...} placeholders and Spring Expression
|
||||
* Language #{...} expressions. For this to work a
|
||||
* {@link ConfigurableBeanFactory} must be supplied to the class constructor.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final ConfigurableBeanFactory configurableBeanFactory;
|
||||
|
||||
private final BeanExpressionContext expressionContext;
|
||||
|
||||
private final Map<MethodParameter, NamedValueInfo> 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<Object> 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<Object> 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<Object> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>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<Object> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>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<Object> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> resolveArgument(MethodParameter parameter, ModelMap model,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
|
||||
Optional<Object> value = exchange.getAttribute(name);
|
||||
return (value.isPresent() ? Mono.just(value.get()) : Mono.just(Collections.emptyMap()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
|
||||
String attributeName = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
|
||||
Optional<Object> optional = exchange.getAttribute(attributeName);
|
||||
Object value = null;
|
||||
if (optional.isPresent()) {
|
||||
value = ((Map<String, String>) 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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<Object> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
|
||||
List<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Class<?>, 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<HttpMessageConverter<?>> messageConverters = Arrays.asList(
|
||||
new CodecHttpMessageConverter<ByteBuffer>(new ByteBufferEncoder(),
|
||||
new ByteBufferDecoder()),
|
||||
new CodecHttpMessageConverter<String>(new StringEncoder(),
|
||||
new StringDecoder()),
|
||||
new CodecHttpMessageConverter<Object>(new Jaxb2Encoder(),
|
||||
new Jaxb2Decoder()),
|
||||
|
||||
List<HttpMessageConverter<?>> converters = Arrays.asList(
|
||||
new CodecHttpMessageConverter<ByteBuffer>(new ByteBufferEncoder(), new ByteBufferDecoder()),
|
||||
new CodecHttpMessageConverter<String>(new StringEncoder(), new StringDecoder()),
|
||||
new CodecHttpMessageConverter<Object>(new Jaxb2Encoder(), new Jaxb2Decoder()),
|
||||
new CodecHttpMessageConverter<Object>(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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Object> 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
|
||||
if (MultiValueMap.class.isAssignableFrom(paramType)) {
|
||||
return Mono.just(queryParams);
|
||||
}
|
||||
else {
|
||||
return Mono.just(queryParams.toSingleValueMap());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
|
||||
List<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<MethodParameter> getMethodParameter() {
|
||||
return Optional.ofNullable(this.parameter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<HandlerResult> mono = hm.invokeForRequest(this.exchange, this.model);
|
||||
HandlerResult value = mono.get();
|
||||
|
||||
@@ -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<Object> mono = this.resolver.resolveArgument(param, null, this.exchange);
|
||||
TestSubscriber<Object> 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<Object> mono = this.resolver.resolveArgument(param, null, this.exchange);
|
||||
assertSame(foo, mono.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNotRequired() throws Exception {
|
||||
MethodParameter param = initMethodParameter(2);
|
||||
Mono<Object> 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<Object> 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<Foo> 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<Foo> optionalFoo) {
|
||||
}
|
||||
|
||||
private static class Foo {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> 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<Object> mono = this.resolver.resolveArgument(this.cookieStringParameter, null, this.exchange);
|
||||
assertEquals("Invalid result", cookie.getValue(), mono.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveCookieDefaultValue() {
|
||||
Mono<Object> 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<Object> mono = resolver.resolveArgument(this.cookieParameter, null, this.exchange);
|
||||
TestSubscriber<Object> 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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> 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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, String> uriTemplateVars = new HashMap<>();
|
||||
uriTemplateVars.put("name1", "value1");
|
||||
uriTemplateVars.put("name2", "value2");
|
||||
this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
||||
|
||||
Mono<Object> mono = this.resolver.resolveArgument(this.paramMap, new ModelMap(), this.exchange);
|
||||
Object result = mono.get();
|
||||
|
||||
assertEquals(uriTemplateVars, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentNoUriVars() throws Exception {
|
||||
Mono<Object> 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<String, String> map,
|
||||
@PathVariable(value = "name") Map<String, String> namedMap,
|
||||
Map<String, String> mapWithoutAnnotat) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, String> uriTemplateVars = new HashMap<>();
|
||||
uriTemplateVars.put("name", "value");
|
||||
this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
||||
|
||||
Mono<Object> 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<Object> mono = this.resolver.resolveArgument(this.paramNamedString, new ModelMap(), this.exchange);
|
||||
TestSubscriber<Object> subscriber = new TestSubscriber<>();
|
||||
mono.subscribeWith(subscriber);
|
||||
subscriber.assertError(ServerErrorException.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handle(@PathVariable(value = "name") String param1, String param2) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> mono = this.resolver.resolveArgument(param, null, this.exchange);
|
||||
TestSubscriber<Object> 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<Object> mono = this.resolver.resolveArgument(param, null, this.exchange);
|
||||
assertSame(foo, mono.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNotRequired() throws Exception {
|
||||
MethodParameter param = initMethodParameter(2);
|
||||
Mono<Object> 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<Object> 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<Foo> optionalFoo) {
|
||||
}
|
||||
|
||||
private static class Foo {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, String> expected = Collections.singletonMap(name, value);
|
||||
this.exchange.getRequest().getHeaders().add(name, value);
|
||||
|
||||
Mono<Object> 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<String, String> expected = new LinkedMultiValueMap<>(1);
|
||||
expected.add(name, value1);
|
||||
expected.add(name, value2);
|
||||
|
||||
Mono<Object> 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<Object> 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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> mono = resolver.resolveArgument(paramNamedValueStringArray, null, this.exchange);
|
||||
TestSubscriber<Object> 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<Object> 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<Object> 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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, String> expected = Collections.singletonMap(name, value);
|
||||
|
||||
Mono<Object> 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<String, String> expected = new LinkedMultiValueMap<>(1);
|
||||
expected.add(name, value1);
|
||||
expected.add(name, value2);
|
||||
|
||||
Mono<Object> 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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> 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<Object> 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<Object> 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<Object> mono = this.resolver.resolveArgument(paramNamedStringArray, null, this.exchange);
|
||||
TestSubscriber<Object> subscriber = new TestSubscriber<>();
|
||||
mono.subscribeWith(subscriber);
|
||||
subscriber.assertError(ServerWebInputException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveSimpleTypeParam() throws Exception {
|
||||
this.exchange.getRequest().getQueryParams().set("stringNotAnnot", "plainValue");
|
||||
Mono<Object> 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<Object> 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<Object> 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<Object> 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<Object> mono = this.resolver.resolveArgument(paramRequired, null, this.exchange);
|
||||
Object result = mono.get();
|
||||
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveOptionalParamValue() throws Exception {
|
||||
Mono<Object> 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<Integer> paramOptional) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> mono = this.resolver.resolveArgument(param, null, this.exchange);
|
||||
TestSubscriber<Object> 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<Object> mono = this.resolver.resolveArgument(param, null, this.exchange);
|
||||
assertSame(foo, mono.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNotRequired() throws Exception {
|
||||
MethodParameter param = initMethodParameter(2);
|
||||
Mono<Object> 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<Object> 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<Foo> optionalFoo) {
|
||||
}
|
||||
|
||||
private static class Foo {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user