From 143b5c89dd7c9e9d603ccb289943ae45e262c8c5 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 27 Jul 2016 16:59:53 -0400 Subject: [PATCH] Add support for rx.Completable as return value --- .../AbstractMessageWriterResultHandler.java | 4 +- .../annotation/ResponseBodyResultHandler.java | 6 ++- .../ResponseEntityResultHandler.java | 9 ++-- .../view/ViewResolutionResultHandler.java | 20 +++++--- .../MessageWriterResultHandlerTests.java | 4 ++ ...pingMessageConversionIntegrationTests.java | 48 +++++++++++++++---- .../ResponseBodyResultHandlerTests.java | 19 +++++++- .../ResponseEntityResultHandlerTests.java | 8 ++++ .../ViewResolutionResultHandlerTests.java | 9 ++++ 9 files changed, 106 insertions(+), 21 deletions(-) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java index 05797de536a..4869cc4f2e2 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java @@ -97,7 +97,9 @@ public abstract class AbstractMessageWriterResultHandler extends ContentNegotiat ResolvableType elementType; if (adapter != null) { publisher = adapter.toPublisher(body); - elementType = ResolvableType.forMethodParameter(bodyType).getGeneric(0); + elementType = adapter.getDescriptor().isNoValue() ? + ResolvableType.forClass(Void.class) : + ResolvableType.forMethodParameter(bodyType).getGeneric(0); } else { publisher = Mono.justOrEmpty(body); diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java index 4ff68303b69..81d9b285ac6 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java @@ -17,10 +17,12 @@ package org.springframework.web.reactive.result.method.annotation; import java.util.List; +import java.util.Optional; import reactor.core.publisher.Mono; import org.springframework.core.MethodParameter; +import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; @@ -101,7 +103,9 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle return true; } else { - if (getReactiveAdapterRegistry().getAdapterFrom(rawClass, result.getReturnValue()) != null) { + Optional optional = result.getReturnValue(); + ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(rawClass, optional); + if (adapter != null && !adapter.getDescriptor().isNoValue()) { ResolvableType genericType = result.getReturnType().getGeneric(0); if (HttpEntity.class.isAssignableFrom(genericType.getRawClass())) { return true; diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 202c02e0d52..0a161208824 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -85,9 +85,12 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand return true; } else { - Optional returnValue = result.getReturnValue(); - ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(returnType, returnValue); - if (adapter != null && !adapter.getDescriptor().isMultiValue()) { + Optional optional = result.getReturnValue(); + ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(returnType, optional); + if (adapter != null && + !adapter.getDescriptor().isMultiValue() && + !adapter.getDescriptor().isNoValue()) { + ResolvableType genericType = result.getReturnType().getGeneric(0); return isSupportedType(genericType.getRawClass()); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index d1a146c205e..670825e1eb1 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -142,12 +142,19 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler if (hasModelAttributeAnnotation(result)) { return true; } - if (isSupportedType(clazz)) { - return true; + Optional optional = result.getReturnValue(); + ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(clazz, optional); + if (adapter != null) { + if (adapter.getDescriptor().isNoValue()) { + return true; + } + else { + clazz = result.getReturnType().getGeneric(0).getRawClass(); + return isSupportedType(clazz); + } } - if (getReactiveAdapterRegistry().getAdapterFrom(clazz, result.getReturnValue()) != null) { - clazz = result.getReturnType().getGeneric(0).getRawClass(); - return isSupportedType(clazz); + else if (isSupportedType(clazz)) { + return true; } return false; } @@ -181,7 +188,8 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler else { valueMono = Mono.empty(); } - elementType = returnType.getGeneric(0); + elementType = adapter.getDescriptor().isNoValue() ? + ResolvableType.forClass(Void.class) : returnType.getGeneric(0); } else { valueMono = Mono.justOrEmpty(result.getReturnValue()); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java index 083c95b26ff..5ea29597f64 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java @@ -34,6 +34,7 @@ import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.TestSubscriber; +import rx.Completable; import rx.Observable; import org.springframework.core.MethodParameter; @@ -110,6 +111,7 @@ public class MessageWriterResultHandlerTests { public void voidReturnType() throws Exception { testVoidReturnType(null, ResolvableType.forType(void.class)); testVoidReturnType(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class)); + testVoidReturnType(Completable.complete(), ResolvableType.forClass(Completable.class)); testVoidReturnType(Flux.empty(), ResolvableType.forClassWithGenerics(Flux.class, Void.class)); testVoidReturnType(Observable.empty(), ResolvableType.forClassWithGenerics(Observable.class, Void.class)); } @@ -269,6 +271,8 @@ public class MessageWriterResultHandlerTests { Mono monoVoid() { return null; } + Completable completable() { return null; } + Flux fluxVoid() { return null; } Observable observableVoid() { return null; } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java index e33d263c4b3..f701ba30451 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import rx.Completable; import rx.Observable; import rx.Single; @@ -54,9 +55,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.config.WebReactiveConfiguration; -import static java.util.Arrays.*; -import static org.junit.Assert.*; -import static org.springframework.http.MediaType.*; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.MediaType.APPLICATION_XML; /** @@ -233,6 +235,24 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size()); } + @Test + public void personCreateWithMono() throws Exception { + ResponseEntity entity = performPost( + "/person-create/mono", JSON, new Person("Robert"), null, Void.class); + + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertEquals(1, getApplicationContext().getBean(PersonCreateController.class).persons.size()); + } + + @Test + public void personCreateWithSingle() throws Exception { + ResponseEntity entity = performPost( + "/person-create/single", JSON, new Person("Robert"), null, Void.class); + + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertEquals(1, getApplicationContext().getBean(PersonCreateController.class).persons.size()); + } + @Test public void personCreateWithFluxJson() throws Exception { ResponseEntity entity = performPost("/person-create/flux", JSON, @@ -415,18 +435,28 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq final List persons = new ArrayList<>(); @PostMapping("/publisher") - public Publisher createWithPublisher(@RequestBody Publisher personStream) { - return Flux.from(personStream).doOnNext(persons::add).then(); + public Publisher createWithPublisher(@RequestBody Publisher publisher) { + return Flux.from(publisher).doOnNext(persons::add).then(); + } + + @PostMapping("/mono") + public Mono createWithMono(@RequestBody Mono mono) { + return mono.doOnNext(persons::add).then(); + } + + @PostMapping("/single") + public Completable createWithSingle(@RequestBody Single single) { + return single.map(persons::add).toCompletable(); } @PostMapping("/flux") - public Mono createWithFlux(@RequestBody Flux personStream) { - return personStream.doOnNext(persons::add).then(); + public Mono createWithFlux(@RequestBody Flux flux) { + return flux.doOnNext(persons::add).then(); } @PostMapping("/observable") - public Observable createWithObservable(@RequestBody Observable personStream) { - return personStream.toList().doOnNext(persons::addAll).flatMap(document -> Observable.empty()); + public Observable createWithObservable(@RequestBody Observable observable) { + return observable.toList().doOnNext(persons::addAll).flatMap(document -> Observable.empty()); } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java index 5f30f81a0a3..49602cf3371 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java @@ -24,6 +24,8 @@ import java.util.List; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Mono; +import rx.Completable; +import rx.Single; import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.CharSequenceEncoder; @@ -50,7 +52,7 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.MockWebSessionManager; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** @@ -106,6 +108,9 @@ public class ResponseBodyResultHandlerTests { 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); } @@ -134,6 +139,18 @@ public class ResponseBodyResultHandlerTests { return null; } + public Mono handleToMonoString() { + return null; + } + + public Single handleToSingleString() { + return null; + } + + public Completable handleToCompletable() { + return null; + } + public ResponseEntity handleToResponseEntity() { return null; } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index 032b318a4e8..6278c150bff 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -28,6 +28,7 @@ import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Mono; import reactor.test.TestSubscriber; +import rx.Completable; import rx.Single; import org.springframework.core.MethodParameter; @@ -117,8 +118,13 @@ public class ResponseEntityResultHandlerTests { type = forClassWithGenerics(CompletableFuture.class, responseEntity(String.class)); assertTrue(this.resultHandler.supports(handlerResult(value, type))); + // False + type = ResolvableType.forClass(String.class); assertFalse(this.resultHandler.supports(handlerResult(value, type))); + + type = ResolvableType.forClass(Completable.class); + assertFalse(this.resultHandler.supports(handlerResult(value, type))); } @Test @@ -212,6 +218,8 @@ public class ResponseEntityResultHandlerTests { CompletableFuture> completableFuture() { return null; } String string() { return null; } + + Completable completable() { return null; } } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java index 4b86da852ed..ff31218d777 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java @@ -33,6 +33,7 @@ import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.TestSubscriber; +import rx.Completable; import rx.Single; import org.springframework.core.MethodParameter; @@ -98,6 +99,8 @@ public class ViewResolutionResultHandlerTests { testSupports(ResolvableType.forClassWithGenerics(Mono.class, View.class), true); testSupports(ResolvableType.forClassWithGenerics(Single.class, String.class), true); testSupports(ResolvableType.forClassWithGenerics(Single.class, View.class), true); + testSupports(ResolvableType.forClassWithGenerics(Mono.class, Void.class), true); + testSupports(ResolvableType.forClass(Completable.class), true); testSupports(ResolvableType.forClass(Model.class), true); testSupports(ResolvableType.forClass(Map.class), true); testSupports(ResolvableType.forClass(TestBean.class), true); @@ -169,6 +172,8 @@ public class ViewResolutionResultHandlerTests { public void defaultViewName() throws Exception { testDefaultViewName(null, ResolvableType.forClass(String.class)); testDefaultViewName(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, String.class)); + testDefaultViewName(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class)); + testDefaultViewName(Completable.complete(), ResolvableType.forClass(Completable.class)); } private void testDefaultViewName(Object returnValue, ResolvableType type) @@ -387,10 +392,14 @@ public class ViewResolutionResultHandlerTests { Mono monoView() { return null; } + Mono monoVoid() { return null; } + Single singleString() { return null; } Single singleView() { return null; } + Completable completable() { return null; } + Model model() { return null; } Map map() { return null; }