Add support for rx.Completable as return value

This commit is contained in:
Rossen Stoyanchev 2016-07-27 16:59:53 -04:00
parent 79bc227c9d
commit 143b5c89dd
9 changed files with 106 additions and 21 deletions

View File

@ -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);

View File

@ -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<Object> 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;

View File

@ -85,9 +85,12 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
return true;
}
else {
Optional<Object> returnValue = result.getReturnValue();
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(returnType, returnValue);
if (adapter != null && !adapter.getDescriptor().isMultiValue()) {
Optional<Object> 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());
}

View File

@ -142,12 +142,19 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
if (hasModelAttributeAnnotation(result)) {
return true;
}
if (isSupportedType(clazz)) {
return true;
Optional<Object> 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());

View File

@ -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<Void> monoVoid() { return null; }
Completable completable() { return null; }
Flux<Void> fluxVoid() { return null; }
Observable<Void> observableVoid() { return null; }

View File

@ -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<Void> 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<Void> 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<Void> entity = performPost("/person-create/flux", JSON,
@ -415,18 +435,28 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq
final List<Person> persons = new ArrayList<>();
@PostMapping("/publisher")
public Publisher<Void> createWithPublisher(@RequestBody Publisher<Person> personStream) {
return Flux.from(personStream).doOnNext(persons::add).then();
public Publisher<Void> createWithPublisher(@RequestBody Publisher<Person> publisher) {
return Flux.from(publisher).doOnNext(persons::add).then();
}
@PostMapping("/mono")
public Mono<Void> createWithMono(@RequestBody Mono<Person> mono) {
return mono.doOnNext(persons::add).then();
}
@PostMapping("/single")
public Completable createWithSingle(@RequestBody Single<Person> single) {
return single.map(persons::add).toCompletable();
}
@PostMapping("/flux")
public Mono<Void> createWithFlux(@RequestBody Flux<Person> personStream) {
return personStream.doOnNext(persons::add).then();
public Mono<Void> createWithFlux(@RequestBody Flux<Person> flux) {
return flux.doOnNext(persons::add).then();
}
@PostMapping("/observable")
public Observable<Void> createWithObservable(@RequestBody Observable<Person> personStream) {
return personStream.toList().doOnNext(persons::addAll).flatMap(document -> Observable.empty());
public Observable<Void> createWithObservable(@RequestBody Observable<Person> observable) {
return observable.toList().doOnNext(persons::addAll).flatMap(document -> Observable.empty());
}
}

View File

@ -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<String> handleToMonoString() {
return null;
}
public Single<String> handleToSingleString() {
return null;
}
public Completable handleToCompletable() {
return null;
}
public ResponseEntity<String> handleToResponseEntity() {
return null;
}

View File

@ -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<ResponseEntity<String>> completableFuture() { return null; }
String string() { return null; }
Completable completable() { return null; }
}
}

View File

@ -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<View> monoView() { return null; }
Mono<Void> monoVoid() { return null; }
Single<String> singleString() { return null; }
Single<View> singleView() { return null; }
Completable completable() { return null; }
Model model() { return null; }
Map map() { return null; }