diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java index 7be7a41ebf..6be6025637 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -25,6 +25,7 @@ import java.util.function.Predicate; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; +import io.reactivex.Maybe; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -43,6 +44,7 @@ import org.springframework.util.ClassUtils; * registered via {@link #registerFluxAdapter} and {@link #registerMonoAdapter}. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 5.0 */ public class ReactiveAdapterRegistry { @@ -296,6 +298,11 @@ public class ReactiveAdapterRegistry { source -> Flowable.fromPublisher(source).toObservable().singleElement().toSingle(), new ReactiveAdapter.Descriptor(false, false, false) ); + registry.registerMonoAdapter(Maybe.class, + source -> Mono.from(((Maybe) source).toFlowable()), + source -> Flowable.fromPublisher(source).toObservable().singleElement(), + new ReactiveAdapter.Descriptor(false, true, false) + ); registry.registerMonoAdapter(io.reactivex.Completable.class, source -> Mono.from(((io.reactivex.Completable) source).toFlowable()), source -> Flowable.fromPublisher(source).toObservable().ignoreElements(), diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java index b8fba38f35..44c44f9d65 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java @@ -18,6 +18,7 @@ package org.springframework.core.convert.support; import java.util.concurrent.CompletableFuture; import io.reactivex.Flowable; +import io.reactivex.Maybe; import org.junit.Before; import org.junit.Test; import org.reactivestreams.Publisher; @@ -59,6 +60,7 @@ public class ReactiveAdapterRegistryTests { testFluxAdapter(Flowable.class); testFluxAdapter(io.reactivex.Observable.class); testMonoAdapter(io.reactivex.Single.class); + testMonoAdapter(Maybe.class); testMonoAdapter(io.reactivex.Completable.class); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java index c9fe985ec7..ea4b371260 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java @@ -24,6 +24,7 @@ import java.util.concurrent.CompletableFuture; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; +import io.reactivex.Maybe; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; @@ -98,6 +99,7 @@ public class HttpEntityArgumentResolverTests { 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))); @@ -161,6 +163,17 @@ public class HttpEntityArgumentResolverTests { .verify(entity.getBody().toFlowable()); } + @Test + public void emptyBodyWithRxJava2Maybe() throws Exception { + ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class)); + HttpEntity> entity = resolveValueWithEmptyBody(type); + + ScriptedSubscriber + .create().expectNextCount(0) + .expectComplete() + .verify(entity.getBody().toFlowable()); + } + @Test public void emptyBodyWithObservable() throws Exception { ResolvableType type = httpEntityType(forClassWithGenerics(Observable.class, String.class)); @@ -244,6 +257,16 @@ public class HttpEntityArgumentResolverTests { assertEquals("line1", httpEntity.getBody().blockingGet()); } + @Test + public void httpEntityWithRxJava2MaybeBody() throws Exception { + String body = "line1"; + ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class)); + HttpEntity> httpEntity = resolveValue(type, body); + + assertEquals(this.request.getHeaders(), httpEntity.getHeaders()); + assertEquals("line1", httpEntity.getBody().blockingGet()); + } + @Test public void httpEntityWithCompletableFutureBody() throws Exception { String body = "line1"; @@ -337,7 +360,8 @@ public class HttpEntityArgumentResolverTests { HttpEntity> monoBody, HttpEntity> fluxBody, HttpEntity> singleBody, - HttpEntity> xJava2SingleBody, + HttpEntity> rxJava2SingleBody, + HttpEntity> rxJava2MaybeBody, HttpEntity> observableBody, HttpEntity> rxJava2ObservableBody, HttpEntity> flowableBody, diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java index e69786f462..1c6f384eec 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java @@ -29,6 +29,7 @@ import java.util.concurrent.CompletableFuture; import javax.xml.bind.annotation.XmlRootElement; import io.reactivex.Flowable; +import io.reactivex.Maybe; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; @@ -159,6 +160,16 @@ public class MessageReaderArgumentResolverTests { assertEquals(new TestBean("f1", "b1"), single.blockingGet()); } + @Test + public void rxJava2MaybeTestBean() throws Exception { + String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}"; + ResolvableType type = forClassWithGenerics(Maybe.class, TestBean.class); + MethodParameter param = this.testMethod.resolveParam(type); + Maybe maybe = resolveValue(param, body); + + assertEquals(new TestBean("f1", "b1"), maybe.blockingGet()); + } + @Test public void observableTestBean() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; @@ -316,6 +327,7 @@ public class MessageReaderArgumentResolverTests { @Validated Flux fluxTestBean, Single singleTestBean, io.reactivex.Single rxJava2SingleTestBean, + Maybe rxJava2MaybeTestBean, Observable observableTestBean, io.reactivex.Observable rxJava2ObservableTestBean, Flowable flowableTestBean, diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java index 3566644051..c3548392ed 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java @@ -22,12 +22,12 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; +import io.reactivex.Maybe; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.subscriber.ScriptedSubscriber; -import rx.Completable; import rx.Observable; import rx.RxReactiveStreams; import rx.Single; @@ -162,6 +162,21 @@ public class RequestBodyArgumentResolverTests { .verify(RxReactiveStreams.toPublisher(single)); } + @Test + public void emptyBodyWithMaybe() throws Exception { + ResolvableType type = forClassWithGenerics(Maybe.class, String.class); + + Maybe maybe = resolveValueWithEmptyBody(type, true); + ScriptedSubscriber.create().expectNextCount(0) + .expectError(ServerWebInputException.class) + .verify(maybe.toFlowable()); + + maybe = resolveValueWithEmptyBody(type, false); + ScriptedSubscriber.create().expectNextCount(0) + .expectComplete() + .verify(maybe.toFlowable()); + } + @Test public void emptyBodyWithObservable() throws Exception { ResolvableType type = forClassWithGenerics(Observable.class, String.class); @@ -239,6 +254,7 @@ public class RequestBodyArgumentResolverTests { @RequestBody Flux flux, @RequestBody Single single, @RequestBody io.reactivex.Single rxJava2Single, + @RequestBody Maybe rxJava2Maybe, @RequestBody Observable obs, @RequestBody io.reactivex.Observable rxjava2Obs, @RequestBody CompletableFuture future, @@ -247,6 +263,7 @@ public class RequestBodyArgumentResolverTests { @RequestBody(required = false) Flux fluxNotRequired, @RequestBody(required = false) Single singleNotRequired, @RequestBody(required = false) io.reactivex.Single rxJava2SingleNotRequired, + @RequestBody(required = false) Maybe rxJava2MaybeNotRequired, @RequestBody(required = false) Observable obsNotRequired, @RequestBody(required = false) io.reactivex.Observable rxjava2ObsNotRequired, @RequestBody(required = false) CompletableFuture futureNotRequired, 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 3d792664aa..364ab268b9 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 @@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import io.reactivex.Flowable; +import io.reactivex.Maybe; import org.junit.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -218,6 +219,13 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq JSON, Person.class).getBody()); } + @Test + public void personTransformWithRxJava2Maybe() throws Exception { + assertEquals(new Person("ROBERT"), + performPost("/person-transform/rxjava2-maybe", JSON, new Person("Robert"), + JSON, Person.class).getBody()); + } + @Test public void personTransformWithPublisher() throws Exception { List req = asList(new Person("Robert"), new Person("Marie")); @@ -509,6 +517,11 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq return personFuture.map(person -> new Person(person.getName().toUpperCase())); } + @PostMapping("/rxjava2-maybe") + public Maybe transformRxJava2Maybe(@RequestBody Maybe personFuture) { + return personFuture.map(person -> new Person(person.getName().toUpperCase())); + } + @PostMapping("/publisher") public Publisher transformPublisher(@RequestBody Publisher persons) { return Flux