Polish method parameter handling
This commit is contained in:
parent
5ae9afd5a5
commit
a7582fcc23
|
@ -25,10 +25,12 @@ import java.util.Set;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
|
@ -72,6 +74,14 @@ public abstract class AbstractHandlerResultHandler implements Ordered {
|
|||
return this.adapterRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to get a ReactiveAdapter for the top-level return value type.
|
||||
*/
|
||||
protected ReactiveAdapter getAdapter(HandlerResult result) {
|
||||
Class<?> returnType = result.getReturnType().getRawClass();
|
||||
return getAdapterRegistry().getAdapter(returnType, result.getReturnValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link RequestedContentTypeResolver}.
|
||||
*/
|
||||
|
|
|
@ -113,11 +113,7 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
|
||||
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyType.resolve());
|
||||
|
||||
ResolvableType elementType = ResolvableType.forMethodParameter(bodyParameter);
|
||||
if (adapter != null) {
|
||||
elementType = elementType.getGeneric(0);
|
||||
}
|
||||
ResolvableType elementType = (adapter != null ? bodyType.getGeneric(0) : bodyType);
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
@ -139,8 +135,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
else {
|
||||
flux = reader.read(elementType, request, readHints);
|
||||
}
|
||||
flux = flux.onErrorResumeWith(ex -> Flux.error(wrapReadError(ex, bodyParameter)));
|
||||
if (checkRequired(adapter, isBodyRequired)) {
|
||||
flux = flux.onErrorResumeWith(ex -> Flux.error(getReadError(bodyParameter, ex)));
|
||||
if (isBodyRequired || !adapter.supportsEmpty()) {
|
||||
flux = flux.switchIfEmpty(Flux.error(getRequiredBodyError(bodyParameter)));
|
||||
}
|
||||
Object[] hints = extractValidationHints(bodyParameter);
|
||||
|
@ -159,8 +155,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
else {
|
||||
mono = reader.readMono(elementType, request, readHints);
|
||||
}
|
||||
mono = mono.otherwise(ex -> Mono.error(wrapReadError(ex, bodyParameter)));
|
||||
if (checkRequired(adapter, isBodyRequired)) {
|
||||
mono = mono.otherwise(ex -> Mono.error(getReadError(bodyParameter, ex)));
|
||||
if (isBodyRequired || (adapter != null && !adapter.supportsEmpty())) {
|
||||
mono = mono.otherwiseIfEmpty(Mono.error(getRequiredBodyError(bodyParameter)));
|
||||
}
|
||||
Object[] hints = extractValidationHints(bodyParameter);
|
||||
|
@ -181,15 +177,11 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes));
|
||||
}
|
||||
|
||||
protected ServerWebInputException wrapReadError(Throwable ex, MethodParameter parameter) {
|
||||
private ServerWebInputException getReadError(MethodParameter parameter, Throwable ex) {
|
||||
return new ServerWebInputException("Failed to read HTTP message", parameter, ex);
|
||||
}
|
||||
|
||||
protected boolean checkRequired(ReactiveAdapter adapter, boolean isBodyRequired) {
|
||||
return adapter != null && !adapter.supportsEmpty() || isBodyRequired;
|
||||
}
|
||||
|
||||
protected ServerWebInputException getRequiredBodyError(MethodParameter parameter) {
|
||||
private ServerWebInputException getRequiredBodyError(MethodParameter parameter) {
|
||||
return new ServerWebInputException("Required request body is missing: " +
|
||||
parameter.getMethod().toGenericString());
|
||||
}
|
||||
|
@ -199,7 +191,7 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
* a (possibly empty) Object[] with validation hints. A return value of
|
||||
* {@code null} indicates that validation is not required.
|
||||
*/
|
||||
protected Object[] extractValidationHints(MethodParameter parameter) {
|
||||
private Object[] extractValidationHints(MethodParameter parameter) {
|
||||
Annotation[] annotations = parameter.getParameterAnnotations();
|
||||
for (Annotation ann : annotations) {
|
||||
Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class);
|
||||
|
@ -211,8 +203,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
protected void validate(Object target, Object[] validationHints,
|
||||
MethodParameter param, BindingContext binding, ServerWebExchange exchange) {
|
||||
private void validate(Object target, Object[] validationHints, MethodParameter param,
|
||||
BindingContext binding, ServerWebExchange exchange) {
|
||||
|
||||
String name = Conventions.getVariableNameForParameter(param);
|
||||
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name);
|
||||
|
|
|
@ -93,21 +93,19 @@ public abstract class AbstractMessageWriterResultHandler extends AbstractHandler
|
|||
@SuppressWarnings("unchecked")
|
||||
protected Mono<Void> writeBody(Object body, MethodParameter bodyParameter, ServerWebExchange exchange) {
|
||||
|
||||
ResolvableType valueType = ResolvableType.forMethodParameter(bodyParameter);
|
||||
Class<?> valueClass = valueType.resolve();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(valueClass, body);
|
||||
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
|
||||
Class<?> bodyClass = bodyType.resolve();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyClass, body);
|
||||
|
||||
Publisher<?> publisher;
|
||||
ResolvableType elementType;
|
||||
if (adapter != null) {
|
||||
publisher = adapter.toPublisher(body);
|
||||
elementType = adapter.isNoValue() ?
|
||||
ResolvableType.forClass(Void.class) : valueType.getGeneric(0);
|
||||
elementType = adapter.isNoValue() ? ResolvableType.forClass(Void.class) : bodyType.getGeneric(0);
|
||||
}
|
||||
else {
|
||||
publisher = Mono.justOrEmpty(body);
|
||||
elementType = (valueClass == null && body != null ?
|
||||
ResolvableType.forInstance(body) : valueType);
|
||||
elementType = (bodyClass == null && body != null ? ResolvableType.forInstance(body) : bodyType);
|
||||
}
|
||||
|
||||
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {
|
||||
|
@ -122,7 +120,7 @@ public abstract class AbstractMessageWriterResultHandler extends AbstractHandler
|
|||
if (messageWriter.canWrite(elementType, bestMediaType)) {
|
||||
return (messageWriter instanceof ServerHttpMessageWriter ?
|
||||
((ServerHttpMessageWriter<?>) messageWriter).write((Publisher) publisher,
|
||||
valueType, elementType, bestMediaType, request, response, Collections.emptyMap()) :
|
||||
bodyType, elementType, bestMediaType, request, response, Collections.emptyMap()) :
|
||||
messageWriter.write((Publisher) publisher, elementType,
|
||||
bestMediaType, response, Collections.emptyMap()));
|
||||
}
|
||||
|
|
|
@ -22,9 +22,7 @@ import reactor.core.publisher.Mono;
|
|||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
@ -69,29 +67,20 @@ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentRes
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> resolveArgument(MethodParameter param, BindingContext bindingContext,
|
||||
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
ResolvableType entityType = ResolvableType.forMethodParameter(param);
|
||||
MethodParameter bodyParameter = new MethodParameter(param);
|
||||
bodyParameter.increaseNestingLevel();
|
||||
Class<?> entityType = parameter.getParameterType();
|
||||
|
||||
return readBody(bodyParameter, false, bindingContext, exchange)
|
||||
.map(body -> createHttpEntity(body, entityType, exchange))
|
||||
.defaultIfEmpty(createHttpEntity(null, entityType, exchange));
|
||||
return readBody(parameter.nested(), false, bindingContext, exchange)
|
||||
.map(body -> createEntity(body, entityType, exchange.getRequest()))
|
||||
.defaultIfEmpty(createEntity(null, entityType, exchange.getRequest()));
|
||||
}
|
||||
|
||||
private Object createHttpEntity(Object body, ResolvableType entityType,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
if (RequestEntity.class == entityType.getRawClass()) {
|
||||
return new RequestEntity<>(body, headers, request.getMethod(), request.getURI());
|
||||
}
|
||||
else {
|
||||
return new HttpEntity<>(body, headers);
|
||||
}
|
||||
private Object createEntity(Object body, Class<?> entityType, ServerHttpRequest request) {
|
||||
return RequestEntity.class.equals(entityType) ?
|
||||
new RequestEntity<>(body, request.getHeaders(), request.getMethod(), request.getURI()) :
|
||||
new HttpEntity<>(body, request.getHeaders());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.springframework.core.ReactiveAdapter;
|
|||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -122,9 +123,10 @@ public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgume
|
|||
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type.resolve());
|
||||
Class<?> valueType = (adapter != null ? type.resolveGeneric(0) : parameter.getParameterType());
|
||||
ResolvableType valueType = (adapter != null ? type.getGeneric(0) : type);
|
||||
|
||||
String name = getAttributeName(valueType, parameter);
|
||||
Mono<?> valueMono = getAttributeMono(name, valueType, parameter, context, exchange);
|
||||
Mono<?> valueMono = getAttributeMono(name, valueType, context.getModel());
|
||||
|
||||
Map<String, Object> model = context.getModel().asMap();
|
||||
MonoProcessor<BindingResult> bindingResultMono = MonoProcessor.create();
|
||||
|
@ -146,10 +148,10 @@ public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgume
|
|||
if (adapter != null) {
|
||||
return adapter.fromPublisher(errors.hasErrors() ?
|
||||
Mono.error(new WebExchangeBindException(parameter, errors)) :
|
||||
Mono.just(value));
|
||||
valueMono);
|
||||
}
|
||||
else {
|
||||
if (errors.hasErrors() && checkErrorsArgument(parameter)) {
|
||||
if (errors.hasErrors() && !hasErrorsArgument(parameter)) {
|
||||
throw new WebExchangeBindException(parameter, errors);
|
||||
}
|
||||
return value;
|
||||
|
@ -158,46 +160,37 @@ public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgume
|
|||
});
|
||||
}
|
||||
|
||||
private String getAttributeName(Class<?> valueType, MethodParameter parameter) {
|
||||
private String getAttributeName(ResolvableType valueType, MethodParameter parameter) {
|
||||
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
|
||||
if (annot != null && StringUtils.hasText(annot.value())) {
|
||||
return annot.value();
|
||||
}
|
||||
// TODO: Conventions does not deal with async wrappers
|
||||
return ClassUtils.getShortNameAsProperty(valueType);
|
||||
return ClassUtils.getShortNameAsProperty(valueType.getRawClass());
|
||||
}
|
||||
|
||||
private Mono<?> getAttributeMono(String attributeName, Class<?> attributeType,
|
||||
MethodParameter param, BindingContext context, ServerWebExchange exchange) {
|
||||
|
||||
Object attribute = context.getModel().asMap().get(attributeName);
|
||||
private Mono<?> getAttributeMono(String attributeName, ResolvableType attributeType, Model model) {
|
||||
Object attribute = model.asMap().get(attributeName);
|
||||
if (attribute == null) {
|
||||
attribute = createAttribute(attributeName, attributeType, param, context, exchange);
|
||||
attribute = BeanUtils.instantiateClass(attributeType.getRawClass());
|
||||
}
|
||||
if (attribute != null) {
|
||||
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute);
|
||||
if (adapterFrom != null) {
|
||||
Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding supports single-value async types.");
|
||||
return Mono.from(adapterFrom.toPublisher(attribute));
|
||||
}
|
||||
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute);
|
||||
if (adapterFrom != null) {
|
||||
Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding supports single-value async types.");
|
||||
return Mono.from(adapterFrom.toPublisher(attribute));
|
||||
}
|
||||
else {
|
||||
return Mono.justOrEmpty(attribute);
|
||||
}
|
||||
return Mono.justOrEmpty(attribute);
|
||||
}
|
||||
|
||||
|
||||
protected Object createAttribute(String attributeName, Class<?> attributeType,
|
||||
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
|
||||
|
||||
return BeanUtils.instantiateClass(attributeType);
|
||||
}
|
||||
|
||||
protected boolean checkErrorsArgument(MethodParameter methodParam) {
|
||||
private boolean hasErrorsArgument(MethodParameter methodParam) {
|
||||
int i = methodParam.getParameterIndex();
|
||||
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
|
||||
return paramTypes.length <= (i + 1) || !Errors.class.isAssignableFrom(paramTypes[i + 1]);
|
||||
return paramTypes.length > i && Errors.class.isAssignableFrom(paramTypes[i + 1]);
|
||||
}
|
||||
|
||||
protected void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {
|
||||
private void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {
|
||||
Annotation[] annotations = parameter.getParameterAnnotations();
|
||||
for (Annotation ann : annotations) {
|
||||
Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class);
|
||||
|
|
|
@ -43,9 +43,10 @@ public class PathVariableMapMethodArgumentResolver implements SyncHandlerMethodA
|
|||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
|
||||
return (ann != null && (Map.class.isAssignableFrom(parameter.getParameterType()))
|
||||
&& !StringUtils.hasText(ann.value()));
|
||||
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
|
||||
return (annotation != null &&
|
||||
Map.class.isAssignableFrom(parameter.getParameterType()) &&
|
||||
!StringUtils.hasText(annotation.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -74,8 +74,8 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe
|
|||
public Mono<Object> resolveArgument(MethodParameter param, BindingContext bindingContext,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
boolean isRequired = param.getParameterAnnotation(RequestBody.class).required();
|
||||
return readBody(param, isRequired, bindingContext, exchange);
|
||||
RequestBody annotation = param.getParameterAnnotation(RequestBody.class);
|
||||
return readBody(param, annotation.required(), bindingContext, exchange);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -55,31 +55,26 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle
|
|||
|
||||
|
||||
/**
|
||||
* Constructor with {@link HttpMessageWriter}s and a
|
||||
* {@code RequestedContentTypeResolver}.
|
||||
*
|
||||
* @param messageWriters writers for serializing to the response body stream
|
||||
* @param contentTypeResolver for resolving the requested content type
|
||||
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
|
||||
* @param writers writers for serializing to the response body
|
||||
* @param resolver to determine the requested content type
|
||||
*/
|
||||
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> messageWriters,
|
||||
RequestedContentTypeResolver contentTypeResolver) {
|
||||
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> writers,
|
||||
RequestedContentTypeResolver resolver) {
|
||||
|
||||
this(messageWriters, contentTypeResolver, new ReactiveAdapterRegistry());
|
||||
this(writers, resolver, new ReactiveAdapterRegistry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with an additional {@link ReactiveAdapterRegistry}.
|
||||
*
|
||||
* @param messageWriters writers for serializing to the response body stream
|
||||
* @param contentTypeResolver for resolving the requested content type
|
||||
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
|
||||
* rx.Single, etc.) to Flux or Mono
|
||||
* Constructor with an {@link ReactiveAdapterRegistry} instance.
|
||||
* @param writers writers for serializing to the response body
|
||||
* @param resolver to determine the requested content type
|
||||
* @param registry for adaptation to reactive types
|
||||
*/
|
||||
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> messageWriters,
|
||||
RequestedContentTypeResolver contentTypeResolver,
|
||||
ReactiveAdapterRegistry adapterRegistry) {
|
||||
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> writers,
|
||||
RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
|
||||
|
||||
super(messageWriters, contentTypeResolver, adapterRegistry);
|
||||
super(writers, resolver, registry);
|
||||
setOrder(100);
|
||||
}
|
||||
|
||||
|
@ -87,32 +82,11 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle
|
|||
@Override
|
||||
public boolean supports(HandlerResult result) {
|
||||
MethodParameter parameter = result.getReturnTypeSource();
|
||||
return hasResponseBodyAnnotation(parameter) && !isHttpEntityType(result);
|
||||
}
|
||||
|
||||
private boolean hasResponseBodyAnnotation(MethodParameter parameter) {
|
||||
Class<?> containingClass = parameter.getContainingClass();
|
||||
return (AnnotationUtils.findAnnotation(containingClass, ResponseBody.class) != null ||
|
||||
parameter.getMethodAnnotation(ResponseBody.class) != null);
|
||||
}
|
||||
|
||||
private boolean isHttpEntityType(HandlerResult result) {
|
||||
Class<?> rawClass = result.getReturnType().getRawClass();
|
||||
if (HttpEntity.class.isAssignableFrom(rawClass)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(rawClass, result.getReturnValue());
|
||||
if (adapter != null && !adapter.isNoValue()) {
|
||||
ResolvableType genericType = result.getReturnType().getGeneric(0);
|
||||
if (HttpEntity.class.isAssignableFrom(genericType.getRawClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
|
||||
Object body = result.getReturnValue().orElse(null);
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.springframework.web.reactive.result.method.annotation;
|
|||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
@ -54,78 +53,61 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
|
|||
|
||||
|
||||
/**
|
||||
* Constructor with {@link HttpMessageWriter}s and a
|
||||
* {@code RequestedContentTypeResolver}.
|
||||
*
|
||||
* @param messageWriters writers for serializing to the response body stream
|
||||
* @param contentTypeResolver for resolving the requested content type
|
||||
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
|
||||
* @param writers writers for serializing to the response body
|
||||
* @param resolver to determine the requested content type
|
||||
*/
|
||||
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> messageWriters,
|
||||
RequestedContentTypeResolver contentTypeResolver) {
|
||||
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> writers,
|
||||
RequestedContentTypeResolver resolver) {
|
||||
|
||||
this(messageWriters, contentTypeResolver, new ReactiveAdapterRegistry());
|
||||
this(writers, resolver, new ReactiveAdapterRegistry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with an additional {@link ReactiveAdapterRegistry}.
|
||||
*
|
||||
* @param messageWriters writers for serializing to the response body stream
|
||||
* @param contentTypeResolver for resolving the requested content type
|
||||
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
|
||||
* rx.Single, etc.) to Flux or Mono
|
||||
* Constructor with an {@link ReactiveAdapterRegistry} instance.
|
||||
* @param writers writers for serializing to the response body
|
||||
* @param resolver to determine the requested content type
|
||||
* @param registry for adaptation to reactive types
|
||||
*/
|
||||
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> messageWriters,
|
||||
RequestedContentTypeResolver contentTypeResolver,
|
||||
ReactiveAdapterRegistry adapterRegistry) {
|
||||
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> writers,
|
||||
RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
|
||||
|
||||
super(messageWriters, contentTypeResolver, adapterRegistry);
|
||||
super(writers, resolver, registry);
|
||||
setOrder(0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(HandlerResult result) {
|
||||
Class<?> returnType = result.getReturnType().getRawClass();
|
||||
if (isSupportedType(returnType)) {
|
||||
if (isSupportedType(result.getReturnType())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(returnType, result.getReturnValue());
|
||||
if (adapter != null && !adapter.isMultiValue() && !adapter.isNoValue()) {
|
||||
ResolvableType genericType = result.getReturnType().getGeneric(0);
|
||||
return isSupportedType(genericType.getRawClass());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
ReactiveAdapter adapter = getAdapter(result);
|
||||
return adapter != null && !adapter.isNoValue() &&
|
||||
isSupportedType(result.getReturnType().getGeneric(0));
|
||||
}
|
||||
|
||||
private boolean isSupportedType(Class<?> clazz) {
|
||||
private boolean isSupportedType(ResolvableType type) {
|
||||
Class<?> clazz = type.getRawClass();
|
||||
return (HttpEntity.class.isAssignableFrom(clazz) && !RequestEntity.class.isAssignableFrom(clazz));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
|
||||
ResolvableType returnType = result.getReturnType();
|
||||
MethodParameter bodyType;
|
||||
|
||||
Mono<?> returnValueMono;
|
||||
Optional<Object> optionalValue = result.getReturnValue();
|
||||
|
||||
Class<?> rawClass = returnType.getRawClass();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(rawClass, optionalValue);
|
||||
MethodParameter bodyParameter;
|
||||
ReactiveAdapter adapter = getAdapter(result);
|
||||
|
||||
if (adapter != null) {
|
||||
Assert.isTrue(!adapter.isMultiValue(), "Only a single ResponseEntity supported");
|
||||
returnValueMono = Mono.from(adapter.toPublisher(optionalValue));
|
||||
bodyType = new MethodParameter(result.getReturnTypeSource());
|
||||
bodyType.increaseNestingLevel();
|
||||
bodyType.increaseNestingLevel();
|
||||
returnValueMono = Mono.from(adapter.toPublisher(result.getReturnValue()));
|
||||
bodyParameter = result.getReturnTypeSource().nested().nested();
|
||||
}
|
||||
else {
|
||||
returnValueMono = Mono.justOrEmpty(optionalValue);
|
||||
bodyType = new MethodParameter(result.getReturnTypeSource());
|
||||
bodyType.increaseNestingLevel();
|
||||
returnValueMono = Mono.justOrEmpty(result.getReturnValue());
|
||||
bodyParameter = result.getReturnTypeSource().nested();
|
||||
}
|
||||
|
||||
return returnValueMono.then(returnValue -> {
|
||||
|
@ -156,7 +138,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
|
|||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
return writeBody(httpEntity.getBody(), bodyType, exchange);
|
||||
return writeBody(httpEntity.getBody(), bodyParameter, exchange);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
@ -94,29 +93,27 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
|
|||
|
||||
|
||||
/**
|
||||
* Constructor with {@link ViewResolver}s and a {@link RequestedContentTypeResolver}.
|
||||
* @param resolvers the resolver to use
|
||||
* @param contentTypeResolver for resolving the requested content type
|
||||
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
|
||||
* @param viewResolvers the resolver to use
|
||||
* @param contentTypeResolver to determine the requested content type
|
||||
*/
|
||||
public ViewResolutionResultHandler(List<ViewResolver> resolvers,
|
||||
public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
|
||||
RequestedContentTypeResolver contentTypeResolver) {
|
||||
|
||||
this(resolvers, contentTypeResolver, new ReactiveAdapterRegistry());
|
||||
this(viewResolvers, contentTypeResolver, new ReactiveAdapterRegistry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with {@code ViewResolver}s tand a {@code ConversionService}.
|
||||
* @param resolvers the resolver to use
|
||||
* @param contentTypeResolver for resolving the requested content type
|
||||
* @param adapterRegistry for adapting from other reactive types (e.g.
|
||||
* rx.Single) to Mono
|
||||
* Constructor with an {@link ReactiveAdapterRegistry} instance.
|
||||
* @param viewResolvers the view resolver to use
|
||||
* @param contentTypeResolver to determine the requested content type
|
||||
* @param registry for adaptation to reactive types
|
||||
*/
|
||||
public ViewResolutionResultHandler(List<ViewResolver> resolvers,
|
||||
RequestedContentTypeResolver contentTypeResolver,
|
||||
ReactiveAdapterRegistry adapterRegistry) {
|
||||
public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
|
||||
RequestedContentTypeResolver contentTypeResolver, ReactiveAdapterRegistry registry) {
|
||||
|
||||
super(contentTypeResolver, adapterRegistry);
|
||||
this.viewResolvers.addAll(resolvers);
|
||||
super(contentTypeResolver, registry);
|
||||
this.viewResolvers.addAll(viewResolvers);
|
||||
AnnotationAwareOrderComparator.sort(this.viewResolvers);
|
||||
}
|
||||
|
||||
|
@ -148,113 +145,97 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
|
|||
|
||||
@Override
|
||||
public boolean supports(HandlerResult result) {
|
||||
Class<?> clazz = result.getReturnType().getRawClass();
|
||||
if (hasModelAttributeAnnotation(result)) {
|
||||
if (hasModelAnnotation(result.getReturnTypeSource())) {
|
||||
return true;
|
||||
}
|
||||
Optional<Object> optional = result.getReturnValue();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(clazz, optional);
|
||||
Class<?> type = result.getReturnType().getRawClass();
|
||||
ReactiveAdapter adapter = getAdapter(result);
|
||||
if (adapter != null) {
|
||||
if (adapter.isNoValue()) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
clazz = result.getReturnType().getGeneric(0).getRawClass();
|
||||
return isSupportedType(clazz);
|
||||
}
|
||||
type = result.getReturnType().getGeneric(0).getRawClass();
|
||||
}
|
||||
else if (isSupportedType(clazz)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return (CharSequence.class.isAssignableFrom(type) || View.class.isAssignableFrom(type) ||
|
||||
Model.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) ||
|
||||
!BeanUtils.isSimpleProperty(type));
|
||||
}
|
||||
|
||||
private boolean hasModelAttributeAnnotation(HandlerResult result) {
|
||||
MethodParameter returnType = result.getReturnTypeSource();
|
||||
return returnType.hasMethodAnnotation(ModelAttribute.class);
|
||||
}
|
||||
|
||||
private boolean isSupportedType(Class<?> clazz) {
|
||||
return (CharSequence.class.isAssignableFrom(clazz) || View.class.isAssignableFrom(clazz) ||
|
||||
Model.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) ||
|
||||
!BeanUtils.isSimpleProperty(clazz));
|
||||
private boolean hasModelAnnotation(MethodParameter parameter) {
|
||||
return parameter.hasMethodAnnotation(ModelAttribute.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
|
||||
|
||||
Mono<Object> returnValueMono;
|
||||
ResolvableType elementType;
|
||||
ResolvableType parameterType = result.getReturnType();
|
||||
|
||||
Optional<Object> optional = result.getReturnValue();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameterType.getRawClass(), optional);
|
||||
Mono<Object> valueMono;
|
||||
ResolvableType valueType;
|
||||
ReactiveAdapter adapter = getAdapter(result);
|
||||
|
||||
if (adapter != null) {
|
||||
Assert.isTrue(!adapter.isMultiValue(), "Only single-value async return type supported.");
|
||||
returnValueMono = optional
|
||||
.map(value -> Mono.from(adapter.toPublisher(value)))
|
||||
.orElse(Mono.empty());
|
||||
elementType = !adapter.isNoValue() ?
|
||||
parameterType.getGeneric(0) : ResolvableType.forClass(Void.class);
|
||||
valueMono = result.getReturnValue()
|
||||
.map(value -> Mono.from(adapter.toPublisher(value))).orElse(Mono.empty());
|
||||
valueType = adapter.isNoValue() ?
|
||||
ResolvableType.forClass(Void.class) : result.getReturnType().getGeneric(0);
|
||||
}
|
||||
else {
|
||||
returnValueMono = Mono.justOrEmpty(result.getReturnValue());
|
||||
elementType = parameterType;
|
||||
valueMono = Mono.justOrEmpty(result.getReturnValue());
|
||||
valueType = result.getReturnType();
|
||||
}
|
||||
|
||||
return returnValueMono
|
||||
return valueMono
|
||||
.otherwiseIfEmpty(exchange.isNotModified() ? Mono.empty() : NO_VALUE_MONO)
|
||||
.then(returnValue -> {
|
||||
|
||||
Mono<List<View>> viewsMono;
|
||||
Model model = result.getModel();
|
||||
MethodParameter parameter = result.getReturnTypeSource();
|
||||
|
||||
Locale acceptLocale = exchange.getRequest().getHeaders().getAcceptLanguageAsLocale();
|
||||
Locale locale = acceptLocale != null ? acceptLocale : Locale.getDefault();
|
||||
|
||||
Class<?> clazz = elementType.getRawClass();
|
||||
Class<?> clazz = valueType.getRawClass();
|
||||
if (clazz == null) {
|
||||
clazz = returnValue.getClass();
|
||||
}
|
||||
|
||||
if (returnValue == NO_VALUE || Void.class.equals(clazz) || void.class.equals(clazz)) {
|
||||
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
|
||||
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
|
||||
}
|
||||
else if (Model.class.isAssignableFrom(clazz)) {
|
||||
model.addAllAttributes(((Model) returnValue).asMap());
|
||||
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
|
||||
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
|
||||
}
|
||||
else if (Map.class.isAssignableFrom(clazz)) {
|
||||
model.addAllAttributes((Map<String, ?>) returnValue);
|
||||
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
|
||||
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
|
||||
}
|
||||
else if (View.class.isAssignableFrom(clazz)) {
|
||||
viewsMono = Mono.just(Collections.singletonList((View) returnValue));
|
||||
}
|
||||
else if (CharSequence.class.isAssignableFrom(clazz) && !hasModelAttributeAnnotation(result)) {
|
||||
else if (CharSequence.class.isAssignableFrom(clazz) && !hasModelAnnotation(parameter)) {
|
||||
viewsMono = resolveViews(returnValue.toString(), locale);
|
||||
}
|
||||
else {
|
||||
String name = getNameForReturnValue(clazz, result.getReturnTypeSource());
|
||||
String name = getNameForReturnValue(clazz, parameter);
|
||||
model.addAttribute(name, returnValue);
|
||||
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
|
||||
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
|
||||
}
|
||||
|
||||
return resolveAsyncAttributes(model.asMap())
|
||||
.doOnSuccess(aVoid -> addBindingResult(result, exchange))
|
||||
.doOnSuccess(aVoid -> addBindingResult(result.getBindingContext(), exchange))
|
||||
.then(viewsMono)
|
||||
.then(views -> render(views, model.asMap(), exchange));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a default view name when a controller leaves the view unspecified.
|
||||
* The default implementation strips the leading and trailing slash from the
|
||||
* as well as any extension and uses that as the view name.
|
||||
* Select a default view name when a controller did not specify it.
|
||||
* Use the request path the leading and trailing slash stripped.
|
||||
*/
|
||||
protected String getDefaultViewName(HandlerResult result, ServerWebExchange exchange) {
|
||||
private String getDefaultViewName(ServerWebExchange exchange) {
|
||||
String path = this.pathHelper.getLookupPathForRequest(exchange);
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
|
@ -332,8 +313,7 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
|
|||
.then();
|
||||
}
|
||||
|
||||
private void addBindingResult(HandlerResult result, ServerWebExchange exchange) {
|
||||
BindingContext context = result.getBindingContext();
|
||||
private void addBindingResult(BindingContext context, ServerWebExchange exchange) {
|
||||
Map<String, Object> model = context.getModel().asMap();
|
||||
model.keySet().stream()
|
||||
.filter(name -> isBindingCandidate(name, model.get(name)))
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.springframework.http.codec.DecoderHttpMessageReader;
|
|||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.method.ResolvableMethod;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
@ -83,23 +84,26 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void supports() throws Exception {
|
||||
testSupports(httpEntityType(String.class));
|
||||
testSupports(httpEntityType(forClassWithGenerics(Mono.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(Single.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(Maybe.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(Flux.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(Observable.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(io.reactivex.Observable.class, String.class)));
|
||||
testSupports(httpEntityType(forClassWithGenerics(Flowable.class, String.class)));
|
||||
testSupports(forClassWithGenerics(RequestEntity.class, String.class));
|
||||
testSupports(this.testMethod.arg(httpEntityType(String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(Mono.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(Single.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(io.reactivex.Single.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(Maybe.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(CompletableFuture.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(Flux.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(Observable.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(io.reactivex.Observable.class, String.class)));
|
||||
testSupports(this.testMethod.arg(httpEntityType(Flowable.class, String.class)));
|
||||
testSupports(this.testMethod.arg(forClassWithGenerics(RequestEntity.class, String.class)));
|
||||
}
|
||||
|
||||
private void testSupports(MethodParameter parameter) {
|
||||
assertTrue(this.resolver.supportsParameter(parameter));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotSupport() throws Exception {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(Mono.class, String.class);
|
||||
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(type)));
|
||||
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(Mono.class, String.class)));
|
||||
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(String.class)));
|
||||
}
|
||||
|
||||
|
@ -113,7 +117,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithMono() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Mono.class, String.class));
|
||||
ResolvableType type = httpEntityType(Mono.class, String.class);
|
||||
HttpEntity<Mono<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
|
||||
|
@ -121,7 +125,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithFlux() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Flux.class, String.class));
|
||||
ResolvableType type = httpEntityType(Flux.class, String.class);
|
||||
HttpEntity<Flux<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
|
||||
|
@ -129,7 +133,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithSingle() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Single.class, String.class));
|
||||
ResolvableType type = httpEntityType(Single.class, String.class);
|
||||
HttpEntity<Single<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
|
||||
|
@ -140,7 +144,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithRxJava2Single() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class));
|
||||
ResolvableType type = httpEntityType(io.reactivex.Single.class, String.class);
|
||||
HttpEntity<io.reactivex.Single<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(entity.getBody().toFlowable())
|
||||
|
@ -151,7 +155,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithRxJava2Maybe() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class));
|
||||
ResolvableType type = httpEntityType(Maybe.class, String.class);
|
||||
HttpEntity<Maybe<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(entity.getBody().toFlowable())
|
||||
|
@ -162,7 +166,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithObservable() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Observable.class, String.class));
|
||||
ResolvableType type = httpEntityType(Observable.class, String.class);
|
||||
HttpEntity<Observable<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
|
||||
|
@ -173,7 +177,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithRxJava2Observable() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Observable.class, String.class));
|
||||
ResolvableType type = httpEntityType(io.reactivex.Observable.class, String.class);
|
||||
HttpEntity<io.reactivex.Observable<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(entity.getBody().toFlowable(BackpressureStrategy.BUFFER))
|
||||
|
@ -184,7 +188,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithFlowable() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Flowable.class, String.class));
|
||||
ResolvableType type = httpEntityType(Flowable.class, String.class);
|
||||
HttpEntity<Flowable<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
StepVerifier.create(entity.getBody())
|
||||
|
@ -195,7 +199,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
|
||||
@Test
|
||||
public void emptyBodyWithCompletableFuture() throws Exception {
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class));
|
||||
ResolvableType type = httpEntityType(CompletableFuture.class, String.class);
|
||||
HttpEntity<CompletableFuture<String>> entity = resolveValueWithEmptyBody(type);
|
||||
|
||||
entity.getBody().whenComplete((body, ex) -> {
|
||||
|
@ -217,7 +221,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
@Test
|
||||
public void httpEntityWithMonoBody() throws Exception {
|
||||
String body = "line1";
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Mono.class, String.class));
|
||||
ResolvableType type = httpEntityType(Mono.class, String.class);
|
||||
HttpEntity<Mono<String>> httpEntity = resolveValue(type, body);
|
||||
|
||||
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
|
||||
|
@ -227,7 +231,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
@Test
|
||||
public void httpEntityWithSingleBody() throws Exception {
|
||||
String body = "line1";
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Single.class, String.class));
|
||||
ResolvableType type = httpEntityType(Single.class, String.class);
|
||||
HttpEntity<Single<String>> httpEntity = resolveValue(type, body);
|
||||
|
||||
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
|
||||
|
@ -237,7 +241,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
@Test
|
||||
public void httpEntityWithRxJava2SingleBody() throws Exception {
|
||||
String body = "line1";
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class));
|
||||
ResolvableType type = httpEntityType(io.reactivex.Single.class, String.class);
|
||||
HttpEntity<io.reactivex.Single<String>> httpEntity = resolveValue(type, body);
|
||||
|
||||
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
|
||||
|
@ -247,7 +251,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
@Test
|
||||
public void httpEntityWithRxJava2MaybeBody() throws Exception {
|
||||
String body = "line1";
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class));
|
||||
ResolvableType type = httpEntityType(Maybe.class, String.class);
|
||||
HttpEntity<Maybe<String>> httpEntity = resolveValue(type, body);
|
||||
|
||||
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
|
||||
|
@ -257,7 +261,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
@Test
|
||||
public void httpEntityWithCompletableFutureBody() throws Exception {
|
||||
String body = "line1";
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class));
|
||||
ResolvableType type = httpEntityType(CompletableFuture.class, String.class);
|
||||
HttpEntity<CompletableFuture<String>> httpEntity = resolveValue(type, body);
|
||||
|
||||
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
|
||||
|
@ -267,7 +271,7 @@ public class HttpEntityArgumentResolverTests {
|
|||
@Test
|
||||
public void httpEntityWithFluxBody() throws Exception {
|
||||
String body = "line1\nline2\nline3\n";
|
||||
ResolvableType type = httpEntityType(forClassWithGenerics(Flux.class, String.class));
|
||||
ResolvableType type = httpEntityType(Flux.class, String.class);
|
||||
HttpEntity<Flux<String>> httpEntity = resolveValue(type, body);
|
||||
|
||||
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
|
||||
|
@ -292,18 +296,13 @@ public class HttpEntityArgumentResolverTests {
|
|||
}
|
||||
|
||||
|
||||
private ResolvableType httpEntityType(Class<?> bodyType) {
|
||||
return httpEntityType(ResolvableType.forClass(bodyType));
|
||||
private ResolvableType httpEntityType(Class<?> bodyType, Class<?>... generics) {
|
||||
return ResolvableType.forClassWithGenerics(HttpEntity.class,
|
||||
ObjectUtils.isEmpty(generics) ?
|
||||
ResolvableType.forClass(bodyType) :
|
||||
ResolvableType.forClassWithGenerics(bodyType, generics));
|
||||
}
|
||||
|
||||
private ResolvableType httpEntityType(ResolvableType type) {
|
||||
return forClassWithGenerics(HttpEntity.class, type);
|
||||
}
|
||||
|
||||
private void testSupports(ResolvableType type) {
|
||||
MethodParameter parameter = this.testMethod.arg(type);
|
||||
assertTrue(this.resolver.supportsParameter(parameter));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T resolveValue(ResolvableType type, String body) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -27,7 +28,6 @@ import rx.Single;
|
|||
|
||||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
|
@ -41,7 +41,10 @@ import org.springframework.web.reactive.HandlerResult;
|
|||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.web.method.ResolvableMethod.on;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ResponseBodyResultHandler}.When adding a test also
|
||||
|
@ -75,22 +78,42 @@ public class ResponseBodyResultHandlerTests {
|
|||
@Test
|
||||
public void supports() throws NoSuchMethodException {
|
||||
Object controller = new TestController();
|
||||
testSupports(controller, "handleToString", true);
|
||||
testSupports(controller, "doWork", false);
|
||||
Method method;
|
||||
|
||||
controller = new TestRestController();
|
||||
testSupports(controller, "handleToString", true);
|
||||
testSupports(controller, "handleToMonoString", true);
|
||||
testSupports(controller, "handleToSingleString", true);
|
||||
testSupports(controller, "handleToCompletable", true);
|
||||
testSupports(controller, "handleToResponseEntity", false);
|
||||
testSupports(controller, "handleToMonoResponseEntity", false);
|
||||
method = on(TestController.class).annotPresent(ResponseBody.class).resolveMethod();
|
||||
testSupports(controller, method);
|
||||
|
||||
method = on(TestController.class).annotNotPresent(ResponseBody.class).resolveMethod();
|
||||
HandlerResult handlerResult = getHandlerResult(controller, method);
|
||||
assertFalse(this.resultHandler.supports(handlerResult));
|
||||
}
|
||||
|
||||
private void testSupports(Object controller, String method, boolean result) throws NoSuchMethodException {
|
||||
HandlerMethod hm = handlerMethod(controller, method);
|
||||
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType());
|
||||
assertEquals(result, this.resultHandler.supports(handlerResult));
|
||||
@Test
|
||||
public void supportsRestController() throws NoSuchMethodException {
|
||||
Object controller = new TestRestController();
|
||||
Method method;
|
||||
|
||||
method = on(TestRestController.class).returning(String.class).resolveMethod();
|
||||
testSupports(controller, method);
|
||||
|
||||
method = on(TestRestController.class).returning(Mono.class, String.class).resolveMethod();
|
||||
testSupports(controller, method);
|
||||
|
||||
method = on(TestRestController.class).returning(Single.class, String.class).resolveMethod();
|
||||
testSupports(controller, method);
|
||||
|
||||
method = on(TestRestController.class).returning(Completable.class).resolveMethod();
|
||||
testSupports(controller, method);
|
||||
}
|
||||
|
||||
private void testSupports(Object controller, Method method) {
|
||||
HandlerResult handlerResult = getHandlerResult(controller, method);
|
||||
assertTrue(this.resultHandler.supports(handlerResult));
|
||||
}
|
||||
|
||||
private HandlerResult getHandlerResult(Object controller, Method method) {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
|
||||
return new HandlerResult(handlerMethod, null, handlerMethod.getReturnType());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -99,10 +122,6 @@ public class ResponseBodyResultHandlerTests {
|
|||
}
|
||||
|
||||
|
||||
private HandlerMethod handlerMethod(Object controller, String method) throws NoSuchMethodException {
|
||||
return new HandlerMethod(controller, controller.getClass().getMethod(method));
|
||||
}
|
||||
|
||||
|
||||
@RestController
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -125,14 +144,6 @@ public class ResponseBodyResultHandlerTests {
|
|||
public Completable handleToCompletable() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ResponseEntity<String> handleToResponseEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mono<ResponseEntity<String>> handleToMonoResponseEntity() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue