Provide rich type information to ConversionService
When using the ConversionService to check and bridge to and from reactive types we now generallly provide the full type information available from method signatures. However that full type information is not always necessary such as when we perform additional checks on the generics of the reactive type (e.g. Mono<ResponseEntity>). This allows us to switch to use DefaultFormattingConversionService instead of GenericConversionService while also ensuring that the CollectionToObjectConverter doesn't think it can convert List<?> to any reactive type. The ObjectToObjectConverter can also interfere because it is smart enough to find the "from(Publisher<?>)" method on Flux and Mono. To make up for that on the response side we now check if a type is assignable to Publisher first in which case it is a simple cast. In turn that means we don't need a PublisherToFluxConverter which can be problematic in its own right because it can convert from Mono to Flux which technically doesn't lose data but switches stream semantics. Issue: #124, #128
This commit is contained in:
parent
fb2e796048
commit
8c76581442
|
@ -1,51 +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.core.convert.support;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class PublisherToFluxConverter implements GenericConverter {
|
||||
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
Set<ConvertiblePair> pairs = new LinkedHashSet<>();
|
||||
pairs.add(new ConvertiblePair(Publisher.class, Flux.class));
|
||||
return pairs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
else if (Publisher.class.isAssignableFrom(sourceType.getType())) {
|
||||
return Flux.from((Publisher)source);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -28,25 +28,25 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.ByteBufferDecoder;
|
||||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.convert.support.PublisherToFluxConverter;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.codec.StringEncoder;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.format.Formatter;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.SseEventEncoder;
|
||||
import org.springframework.http.codec.json.JacksonJsonDecoder;
|
||||
import org.springframework.http.codec.json.JacksonJsonEncoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2Decoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2Encoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.codec.StringEncoder;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.format.Formatter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.SseEventEncoder;
|
||||
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
|
||||
import org.springframework.http.converter.reactive.HttpMessageConverter;
|
||||
import org.springframework.http.converter.reactive.ResourceHttpMessageConverter;
|
||||
|
@ -271,17 +271,13 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
|
|||
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
}
|
||||
|
||||
// TODO: switch to DefaultFormattingConversionService
|
||||
|
||||
@Bean
|
||||
public GenericConversionService mvcConversionService() {
|
||||
GenericConversionService service = new GenericConversionService();
|
||||
public FormattingConversionService mvcConversionService() {
|
||||
FormattingConversionService service = new DefaultFormattingConversionService();
|
||||
addFormatters(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
// TODO: switch to FormatterRegistry
|
||||
|
||||
/**
|
||||
* Override to add custom {@link Converter}s and {@link Formatter}s.
|
||||
* <p>By default this method method registers:
|
||||
|
@ -290,9 +286,8 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
|
|||
* <li>{@link ReactorToRxJava1Converter}
|
||||
* </ul>
|
||||
*/
|
||||
protected void addFormatters(ConverterRegistry registry) {
|
||||
protected void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverter(new MonoToCompletableFutureConverter());
|
||||
registry.addConverter(new PublisherToFluxConverter());
|
||||
if (DependencyUtils.hasRxJava1()) {
|
||||
registry.addConverter(new ReactorToRxJava1Converter());
|
||||
}
|
||||
|
|
|
@ -18,12 +18,14 @@ package org.springframework.web.reactive.result;
|
|||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.HandlerResultHandler;
|
||||
|
@ -42,6 +44,11 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
*/
|
||||
public class SimpleResultHandler implements Ordered, HandlerResultHandler {
|
||||
|
||||
protected static final TypeDescriptor MONO_TYPE = TypeDescriptor.valueOf(Mono.class);
|
||||
|
||||
protected static final TypeDescriptor FLUX_TYPE = TypeDescriptor.valueOf(Flux.class);
|
||||
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
private int order = Ordered.LOWEST_PRECEDENCE;
|
||||
|
@ -83,14 +90,19 @@ public class SimpleResultHandler implements Ordered, HandlerResultHandler {
|
|||
if (Void.TYPE.equals(type.getRawClass())) {
|
||||
return true;
|
||||
}
|
||||
if (getConversionService().canConvert(type.getRawClass(), Mono.class) ||
|
||||
getConversionService().canConvert(type.getRawClass(), Flux.class)) {
|
||||
TypeDescriptor source = new TypeDescriptor(result.getReturnTypeSource());
|
||||
if (Publisher.class.isAssignableFrom(type.getRawClass()) ||
|
||||
canConvert(source, MONO_TYPE) || canConvert(source, FLUX_TYPE)) {
|
||||
Class<?> clazz = result.getReturnType().getGeneric(0).getRawClass();
|
||||
return Void.class.equals(clazz);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean canConvert(TypeDescriptor source, TypeDescriptor target) {
|
||||
return getConversionService().canConvert(source, target);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
|
||||
|
@ -98,19 +110,14 @@ public class SimpleResultHandler implements Ordered, HandlerResultHandler {
|
|||
if (!optional.isPresent()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
Object returnValue = optional.get();
|
||||
if (returnValue instanceof Mono) {
|
||||
return (Mono<Void>) returnValue;
|
||||
}
|
||||
|
||||
ResolvableType returnType = result.getReturnType();
|
||||
if (getConversionService().canConvert(returnType.getRawClass(), Mono.class)) {
|
||||
return this.conversionService.convert(returnValue, Mono.class);
|
||||
}
|
||||
else {
|
||||
return this.conversionService.convert(returnValue, Flux.class).single();
|
||||
Object value = optional.get();
|
||||
if (Publisher.class.isAssignableFrom(result.getReturnType().getRawClass())) {
|
||||
return Mono.from((Publisher<?>) value).then();
|
||||
}
|
||||
TypeDescriptor source = new TypeDescriptor(result.getReturnTypeSource());
|
||||
return canConvert(source, MONO_TYPE) ?
|
||||
((Mono<Void>) getConversionService().convert(value, source, MONO_TYPE)) :
|
||||
((Flux<Void>) getConversionService().convert(value, source, FLUX_TYPE)).single();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,8 +22,10 @@ import org.reactivestreams.Publisher;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.reactive.HttpMessageConverter;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
|
@ -41,6 +43,11 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
*/
|
||||
public abstract class AbstractMessageConverterResultHandler extends ContentNegotiatingResultHandlerSupport {
|
||||
|
||||
protected static final TypeDescriptor MONO_TYPE = TypeDescriptor.valueOf(Mono.class);
|
||||
|
||||
protected static final TypeDescriptor FLUX_TYPE = TypeDescriptor.valueOf(Flux.class);
|
||||
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
|
||||
|
@ -70,28 +77,34 @@ public abstract class AbstractMessageConverterResultHandler extends ContentNegot
|
|||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Mono<Void> writeBody(ServerWebExchange exchange, Object body, ResolvableType bodyType) {
|
||||
protected Mono<Void> writeBody(ServerWebExchange exchange, Object body,
|
||||
ResolvableType bodyType, MethodParameter bodyTypeParameter) {
|
||||
|
||||
boolean convertToFlux = getConversionService().canConvert(bodyType.getRawClass(), Flux.class);
|
||||
boolean convertToMono = getConversionService().canConvert(bodyType.getRawClass(), Mono.class);
|
||||
Publisher<?> publisher = null;
|
||||
ResolvableType elementType;
|
||||
|
||||
ResolvableType elementType = convertToFlux || convertToMono ? bodyType.getGeneric(0) : bodyType;
|
||||
|
||||
Publisher<?> publisher;
|
||||
if (body == null) {
|
||||
publisher = Mono.empty();
|
||||
}
|
||||
else if (convertToMono) {
|
||||
publisher = getConversionService().convert(body, Mono.class);
|
||||
}
|
||||
else if (convertToFlux) {
|
||||
publisher = getConversionService().convert(body, Flux.class);
|
||||
if (Publisher.class.isAssignableFrom(bodyType.getRawClass())) {
|
||||
publisher = (Publisher<?>) body;
|
||||
}
|
||||
else {
|
||||
publisher = Mono.just(body);
|
||||
TypeDescriptor descriptor = new TypeDescriptor(bodyTypeParameter);
|
||||
if (getConversionService().canConvert(descriptor, MONO_TYPE)) {
|
||||
publisher = (Publisher<?>) getConversionService().convert(body, descriptor, MONO_TYPE);
|
||||
}
|
||||
else if (getConversionService().canConvert(descriptor, FLUX_TYPE)) {
|
||||
publisher = (Publisher<?>) getConversionService().convert(body, descriptor, FLUX_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
if (Void.class.equals(elementType.getRawClass())) {
|
||||
if (publisher != null) {
|
||||
elementType = bodyType.getGeneric(0);
|
||||
}
|
||||
else {
|
||||
elementType = bodyType;
|
||||
publisher = Mono.justOrEmpty(body);
|
||||
}
|
||||
|
||||
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {
|
||||
return Mono.from((Publisher<Void>) publisher);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.springframework.core.MethodParameter;
|
|||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.reactive.HttpMessageConverter;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
@ -57,6 +58,11 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
|||
*/
|
||||
public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private static final TypeDescriptor MONO_TYPE = TypeDescriptor.valueOf(Mono.class);
|
||||
|
||||
private static final TypeDescriptor FLUX_TYPE = TypeDescriptor.valueOf(Flux.class);
|
||||
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
@ -123,8 +129,9 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve
|
|||
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
|
||||
boolean convertFromMono = getConversionService().canConvert(Mono.class, type.getRawClass());
|
||||
boolean convertFromFlux = getConversionService().canConvert(Flux.class, type.getRawClass());
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(parameter);
|
||||
boolean convertFromMono = getConversionService().canConvert(MONO_TYPE, typeDescriptor);
|
||||
boolean convertFromFlux = getConversionService().canConvert(FLUX_TYPE, typeDescriptor);
|
||||
|
||||
ResolvableType elementType = convertFromMono || convertFromFlux ? type.getGeneric(0) : type;
|
||||
|
||||
|
@ -141,7 +148,7 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve
|
|||
if (this.validator != null) {
|
||||
flux = flux.map(applyValidationIfApplicable(parameter));
|
||||
}
|
||||
return Mono.just(this.conversionService.convert(flux, type.getRawClass()));
|
||||
return Mono.just(getConversionService().convert(flux, FLUX_TYPE, typeDescriptor));
|
||||
}
|
||||
else {
|
||||
Mono<?> mono = converter.readOne(elementType, request);
|
||||
|
@ -151,7 +158,7 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve
|
|||
if (!convertFromMono) {
|
||||
return mono.map(value-> value); // TODO: MonoToObjectConverter
|
||||
}
|
||||
return Mono.just(this.conversionService.convert(mono, type.getRawClass()));
|
||||
return Mono.just(getConversionService().convert(mono, MONO_TYPE, typeDescriptor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,8 @@ public class ResponseBodyResultHandler extends AbstractMessageConverterResultHan
|
|||
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
|
||||
Object body = result.getReturnValue().orElse(null);
|
||||
ResolvableType bodyType = result.getReturnType();
|
||||
return writeBody(exchange, body, bodyType);
|
||||
MethodParameter bodyTypeParameter = result.getReturnTypeSource();
|
||||
return writeBody(exchange, body, bodyType, bodyTypeParameter);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.Optional;
|
|||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.http.HttpEntity;
|
||||
|
@ -85,7 +86,6 @@ public class ResponseEntityResultHandler extends AbstractMessageConverterResultH
|
|||
else if (getConversionService().canConvert(returnType.getRawClass(), Mono.class)) {
|
||||
ResolvableType genericType = result.getReturnType().getGeneric(0);
|
||||
return isSupportedType(genericType);
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -100,17 +100,25 @@ public class ResponseEntityResultHandler extends AbstractMessageConverterResultH
|
|||
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
|
||||
|
||||
ResolvableType returnType = result.getReturnType();
|
||||
Mono<?> returnValueMono;
|
||||
ResolvableType bodyType;
|
||||
|
||||
ResolvableType bodyType;
|
||||
MethodParameter bodyTypeParameter;
|
||||
|
||||
Mono<?> returnValueMono;
|
||||
Optional<Object> optional = result.getReturnValue();
|
||||
|
||||
if (optional.isPresent() && getConversionService().canConvert(returnType.getRawClass(), Mono.class)) {
|
||||
returnValueMono = getConversionService().convert(optional.get(), Mono.class);
|
||||
bodyType = returnType.getGeneric(0).getGeneric(0);
|
||||
bodyType = returnType.getGeneric(0, 0);
|
||||
bodyTypeParameter = new MethodParameter(result.getReturnTypeSource());
|
||||
bodyTypeParameter.increaseNestingLevel();
|
||||
bodyTypeParameter.increaseNestingLevel();
|
||||
}
|
||||
else {
|
||||
returnValueMono = Mono.justOrEmpty(optional);
|
||||
bodyType = returnType.getGeneric(0);
|
||||
bodyTypeParameter = new MethodParameter(result.getReturnTypeSource());
|
||||
bodyTypeParameter.increaseNestingLevel();
|
||||
}
|
||||
|
||||
return returnValueMono.then(returnValue -> {
|
||||
|
@ -132,7 +140,7 @@ public class ResponseEntityResultHandler extends AbstractMessageConverterResultH
|
|||
.forEach(entry -> responseHeaders.put(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
return writeBody(exchange, httpEntity.getBody(), bodyType);
|
||||
return writeBody(exchange, httpEntity.getBody(), bodyType, bodyTypeParameter);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.reactive.result;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Before;
|
||||
|
@ -26,10 +27,10 @@ import rx.Observable;
|
|||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.PublisherToFluxConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -46,16 +47,15 @@ public class SimpleResultHandlerTests {
|
|||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
conversionService.addConverter(new MonoToCompletableFutureConverter());
|
||||
conversionService.addConverter(new PublisherToFluxConverter());
|
||||
conversionService.addConverter(new ReactorToRxJava1Converter());
|
||||
this.resultHandler = new SimpleResultHandler(conversionService);
|
||||
FormattingConversionService service = new DefaultFormattingConversionService();
|
||||
service.addConverter(new MonoToCompletableFutureConverter());
|
||||
service.addConverter(new ReactorToRxJava1Converter());
|
||||
this.resultHandler = new SimpleResultHandler(service);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void supportsWithConversionService() throws NoSuchMethodException {
|
||||
public void supports() throws NoSuchMethodException {
|
||||
testSupports(ResolvableType.forClass(void.class), true);
|
||||
testSupports(ResolvableType.forClassWithGenerics(Publisher.class, Void.class), true);
|
||||
testSupports(ResolvableType.forClassWithGenerics(Flux.class, Void.class), true);
|
||||
|
@ -66,6 +66,11 @@ public class SimpleResultHandlerTests {
|
|||
testSupports(ResolvableType.forClassWithGenerics(Publisher.class, String.class), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsUsesGenericTypeInformation() throws Exception {
|
||||
testSupports(ResolvableType.forClassWithGenerics(List.class, Void.class), false);
|
||||
}
|
||||
|
||||
private void testSupports(ResolvableType type, boolean result) {
|
||||
MethodParameter param = ResolvableMethod.on(TestController.class).returning(type).resolveReturnType();
|
||||
HandlerResult handlerResult = new HandlerResult(new TestController(), null, param);
|
||||
|
@ -90,6 +95,8 @@ public class SimpleResultHandlerTests {
|
|||
|
||||
public Publisher<Void> publisher() { return null; }
|
||||
|
||||
public List<Void> list() { return null; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,12 +36,12 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.core.test.TestSubscriber;
|
||||
import rx.Observable;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.codec.StringEncoder;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.PublisherToFluxConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
@ -58,6 +58,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
|||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.result.ResolvableMethod;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.MockWebSessionManager;
|
||||
|
@ -92,8 +93,8 @@ public class MessageConverterResultHandlerTests {
|
|||
@Test // SPR-12894
|
||||
public void useDefaultContentType() throws Exception {
|
||||
Resource body = new ClassPathResource("logo.png", getClass());
|
||||
ResolvableType bodyType = ResolvableType.forType(Resource.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
|
||||
ResolvableType type = ResolvableType.forType(Resource.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertEquals("image/x-png", this.response.getHeaders().getFirst("Content-Type"));
|
||||
}
|
||||
|
@ -104,22 +105,22 @@ public class MessageConverterResultHandlerTests {
|
|||
Collections.singleton(APPLICATION_JSON));
|
||||
|
||||
String body = "foo";
|
||||
ResolvableType bodyType = ResolvableType.forType(String.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
|
||||
ResolvableType type = ResolvableType.forType(String.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void voidReturnType() throws Exception {
|
||||
testVoidReturnType(null, ResolvableType.forType(Void.class));
|
||||
testVoidReturnType(null, ResolvableType.forType(void.class));
|
||||
testVoidReturnType(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class));
|
||||
testVoidReturnType(Flux.empty(), ResolvableType.forClassWithGenerics(Flux.class, Void.class));
|
||||
testVoidReturnType(Observable.empty(), ResolvableType.forClassWithGenerics(Observable.class, Void.class));
|
||||
}
|
||||
|
||||
private void testVoidReturnType(Object body, ResolvableType bodyType) {
|
||||
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
|
||||
private void testVoidReturnType(Object body, ResolvableType type) {
|
||||
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertNull(this.response.getHeaders().get("Content-Type"));
|
||||
assertNull(this.response.getBody());
|
||||
|
@ -128,10 +129,10 @@ public class MessageConverterResultHandlerTests {
|
|||
@Test // SPR-13135
|
||||
public void unsupportedReturnType() throws Exception {
|
||||
ByteArrayOutputStream body = new ByteArrayOutputStream();
|
||||
ResolvableType bodyType = ResolvableType.forType(OutputStream.class);
|
||||
ResolvableType type = ResolvableType.forType(OutputStream.class);
|
||||
|
||||
HttpMessageConverter<?> converter = new CodecHttpMessageConverter<>(new ByteBufferEncoder());
|
||||
Mono<Void> mono = createResultHandler(converter).writeBody(this.exchange, body, bodyType);
|
||||
Mono<Void> mono = createResultHandler(converter).writeBody(this.exchange, body, type, returnType(type));
|
||||
|
||||
TestSubscriber.subscribe(mono).assertError(IllegalStateException.class);
|
||||
}
|
||||
|
@ -139,8 +140,8 @@ public class MessageConverterResultHandlerTests {
|
|||
@Test // SPR-12811
|
||||
public void jacksonTypeOfListElement() throws Exception {
|
||||
List<ParentClass> body = Arrays.asList(new Foo("foo"), new Bar("bar"));
|
||||
ResolvableType bodyType = ResolvableType.forClassWithGenerics(List.class, ParentClass.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ParentClass.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
|
||||
assertResponseBody("[{\"type\":\"foo\",\"parentProperty\":\"foo\"}," +
|
||||
|
@ -151,8 +152,8 @@ public class MessageConverterResultHandlerTests {
|
|||
@Ignore
|
||||
public void jacksonTypeWithSubType() throws Exception {
|
||||
SimpleBean body = new SimpleBean(123L, "foo");
|
||||
ResolvableType bodyType = ResolvableType.forClass(Identifiable.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
|
||||
ResolvableType type = ResolvableType.forClass(Identifiable.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
|
||||
assertResponseBody("{\"id\":123,\"name\":\"foo\"}");
|
||||
|
@ -162,14 +163,18 @@ public class MessageConverterResultHandlerTests {
|
|||
@Ignore
|
||||
public void jacksonTypeWithSubTypeOfListElement() throws Exception {
|
||||
List<SimpleBean> body = Arrays.asList(new SimpleBean(123L, "foo"), new SimpleBean(456L, "bar"));
|
||||
ResolvableType bodyType = ResolvableType.forClassWithGenerics(List.class, Identifiable.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, Identifiable.class);
|
||||
this.resultHandler.writeBody(this.exchange, body, type, returnType(type)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
|
||||
assertResponseBody("[{\"id\":123,\"name\":\"foo\"},{\"id\":456,\"name\":\"bar\"}]");
|
||||
}
|
||||
|
||||
|
||||
private MethodParameter returnType(ResolvableType bodyType) {
|
||||
return ResolvableMethod.on(TestController.class).returning(bodyType).resolveReturnType();
|
||||
}
|
||||
|
||||
private AbstractMessageConverterResultHandler createResultHandler(HttpMessageConverter<?>... converters) {
|
||||
List<HttpMessageConverter<?>> converterList;
|
||||
if (ObjectUtils.isEmpty(converters)) {
|
||||
|
@ -186,7 +191,6 @@ public class MessageConverterResultHandlerTests {
|
|||
|
||||
GenericConversionService service = new GenericConversionService();
|
||||
service.addConverter(new MonoToCompletableFutureConverter());
|
||||
service.addConverter(new PublisherToFluxConverter());
|
||||
service.addConverter(new ReactorToRxJava1Converter());
|
||||
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
|
||||
|
@ -267,4 +271,29 @@ public class MessageConverterResultHandlerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class TestController {
|
||||
|
||||
Resource resource() { return null; }
|
||||
|
||||
String string() { return null; }
|
||||
|
||||
void voidReturn() { }
|
||||
|
||||
Mono<Void> monoVoid() { return null; }
|
||||
|
||||
Flux<Void> fluxVoid() { return null; }
|
||||
|
||||
Observable<Void> observableVoid() { return null; }
|
||||
|
||||
OutputStream outputStream() { return null; }
|
||||
|
||||
List<ParentClass> listParentClass() { return null; }
|
||||
|
||||
Identifiable identifiable() { return null; }
|
||||
|
||||
List<Identifiable> listIdentifiable() { return null; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,16 +42,16 @@ import org.springframework.core.MethodParameter;
|
|||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.convert.support.PublisherToFluxConverter;
|
||||
import org.springframework.http.codec.json.JacksonJsonDecoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.json.JacksonJsonDecoder;
|
||||
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
|
||||
import org.springframework.http.converter.reactive.HttpMessageConverter;
|
||||
import org.springframework.http.server.reactive.MockServerHttpRequest;
|
||||
|
@ -202,23 +202,29 @@ public class RequestBodyArgumentResolverTests {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T resolveValue(String paramName, Class<T> valueType, String body) {
|
||||
|
||||
this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
this.request.writeWith(Flux.just(dataBuffer(body)));
|
||||
|
||||
Mono<Object> result = this.resolver.resolveArgument(parameter(paramName), this.model, this.exchange);
|
||||
Object value = result.block(Duration.ofSeconds(5));
|
||||
|
||||
assertNotNull(value);
|
||||
assertTrue("Actual type: " + value.getClass(), valueType.isAssignableFrom(value.getClass()));
|
||||
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
private RequestBodyArgumentResolver resolver(Decoder<?>... decoders) {
|
||||
|
||||
List<HttpMessageConverter<?>> converters = new ArrayList<>();
|
||||
Arrays.asList(decoders).forEach(decoder -> converters.add(new CodecHttpMessageConverter<>(decoder)));
|
||||
GenericConversionService service = new GenericConversionService();
|
||||
|
||||
FormattingConversionService service = new DefaultFormattingConversionService();
|
||||
service.addConverter(new MonoToCompletableFutureConverter());
|
||||
service.addConverter(new PublisherToFluxConverter());
|
||||
service.addConverter(new ReactorToRxJava1Converter());
|
||||
|
||||
return new RequestBodyArgumentResolver(converters, service, new TestBeanValidator());
|
||||
}
|
||||
|
||||
|
|
|
@ -28,10 +28,10 @@ import reactor.core.publisher.Mono;
|
|||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.codec.StringEncoder;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.PublisherToFluxConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.json.JacksonJsonEncoder;
|
||||
|
@ -99,9 +99,8 @@ public class ResponseBodyResultHandlerTests {
|
|||
else {
|
||||
converterList = Arrays.asList(converters);
|
||||
}
|
||||
GenericConversionService service = new GenericConversionService();
|
||||
FormattingConversionService service = new DefaultFormattingConversionService();
|
||||
service.addConverter(new MonoToCompletableFutureConverter());
|
||||
service.addConverter(new PublisherToFluxConverter());
|
||||
service.addConverter(new ReactorToRxJava1Converter());
|
||||
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
|
||||
|
||||
|
|
|
@ -33,11 +33,11 @@ import org.springframework.core.MethodParameter;
|
|||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.codec.StringEncoder;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.PublisherToFluxConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
@ -102,9 +102,8 @@ public class ResponseEntityResultHandlerTests {
|
|||
else {
|
||||
converterList = Arrays.asList(converters);
|
||||
}
|
||||
GenericConversionService service = new GenericConversionService();
|
||||
FormattingConversionService service = new DefaultFormattingConversionService();
|
||||
service.addConverter(new MonoToCompletableFutureConverter());
|
||||
service.addConverter(new PublisherToFluxConverter());
|
||||
service.addConverter(new ReactorToRxJava1Converter());
|
||||
|
||||
|
||||
|
|
|
@ -38,13 +38,13 @@ import rx.Single;
|
|||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.PublisherToFluxConverter;
|
||||
import org.springframework.core.convert.support.MonoToCompletableFutureConverter;
|
||||
import org.springframework.core.convert.support.ReactorToRxJava1Converter;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.MockServerHttpRequest;
|
||||
|
@ -264,10 +264,12 @@ public class ViewResolutionResultHandlerTests {
|
|||
}
|
||||
|
||||
private ViewResolutionResultHandler createResultHandler(List<View> defaultViews, ViewResolver... resolvers) {
|
||||
ConfigurableConversionService service = new DefaultConversionService();
|
||||
|
||||
FormattingConversionService service = new DefaultFormattingConversionService();
|
||||
service.addConverter(new MonoToCompletableFutureConverter());
|
||||
service.addConverter(new ReactorToRxJava1Converter());
|
||||
service.addConverter(new PublisherToFluxConverter());
|
||||
List<ViewResolver> resolverList = Arrays.asList(resolvers);
|
||||
|
||||
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolverList, service);
|
||||
handler.setDefaultViews(defaultViews);
|
||||
return handler;
|
||||
|
|
Loading…
Reference in New Issue