Skip to content

Commit 143b5c8

Browse files
committed
Add support for rx.Completable as return value
1 parent 79bc227 commit 143b5c8

File tree

9 files changed

+106
-21
lines changed

9 files changed

+106
-21
lines changed

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ protected Mono<Void> writeBody(Object body, MethodParameter bodyType, ServerWebE
9797
ResolvableType elementType;
9898
if (adapter != null) {
9999
publisher = adapter.toPublisher(body);
100-
elementType = ResolvableType.forMethodParameter(bodyType).getGeneric(0);
100+
elementType = adapter.getDescriptor().isNoValue() ?
101+
ResolvableType.forClass(Void.class) :
102+
ResolvableType.forMethodParameter(bodyType).getGeneric(0);
101103
}
102104
else {
103105
publisher = Mono.justOrEmpty(body);

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
package org.springframework.web.reactive.result.method.annotation;
1818

1919
import java.util.List;
20+
import java.util.Optional;
2021

2122
import reactor.core.publisher.Mono;
2223

2324
import org.springframework.core.MethodParameter;
25+
import org.springframework.core.ReactiveAdapter;
2426
import org.springframework.core.ReactiveAdapterRegistry;
2527
import org.springframework.core.ResolvableType;
2628
import org.springframework.core.annotation.AnnotationUtils;
@@ -101,7 +103,9 @@ private boolean isHttpEntityType(HandlerResult result) {
101103
return true;
102104
}
103105
else {
104-
if (getReactiveAdapterRegistry().getAdapterFrom(rawClass, result.getReturnValue()) != null) {
106+
Optional<Object> optional = result.getReturnValue();
107+
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(rawClass, optional);
108+
if (adapter != null && !adapter.getDescriptor().isNoValue()) {
105109
ResolvableType genericType = result.getReturnType().getGeneric(0);
106110
if (HttpEntity.class.isAssignableFrom(genericType.getRawClass())) {
107111
return true;

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,12 @@ public boolean supports(HandlerResult result) {
8585
return true;
8686
}
8787
else {
88-
Optional<Object> returnValue = result.getReturnValue();
89-
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(returnType, returnValue);
90-
if (adapter != null && !adapter.getDescriptor().isMultiValue()) {
88+
Optional<Object> optional = result.getReturnValue();
89+
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(returnType, optional);
90+
if (adapter != null &&
91+
!adapter.getDescriptor().isMultiValue() &&
92+
!adapter.getDescriptor().isNoValue()) {
93+
9194
ResolvableType genericType = result.getReturnType().getGeneric(0);
9295
return isSupportedType(genericType.getRawClass());
9396
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,19 @@ public boolean supports(HandlerResult result) {
142142
if (hasModelAttributeAnnotation(result)) {
143143
return true;
144144
}
145-
if (isSupportedType(clazz)) {
146-
return true;
145+
Optional<Object> optional = result.getReturnValue();
146+
ReactiveAdapter adapter = getReactiveAdapterRegistry().getAdapterFrom(clazz, optional);
147+
if (adapter != null) {
148+
if (adapter.getDescriptor().isNoValue()) {
149+
return true;
150+
}
151+
else {
152+
clazz = result.getReturnType().getGeneric(0).getRawClass();
153+
return isSupportedType(clazz);
154+
}
147155
}
148-
if (getReactiveAdapterRegistry().getAdapterFrom(clazz, result.getReturnValue()) != null) {
149-
clazz = result.getReturnType().getGeneric(0).getRawClass();
150-
return isSupportedType(clazz);
156+
else if (isSupportedType(clazz)) {
157+
return true;
151158
}
152159
return false;
153160
}
@@ -181,7 +188,8 @@ public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result)
181188
else {
182189
valueMono = Mono.empty();
183190
}
184-
elementType = returnType.getGeneric(0);
191+
elementType = adapter.getDescriptor().isNoValue() ?
192+
ResolvableType.forClass(Void.class) : returnType.getGeneric(0);
185193
}
186194
else {
187195
valueMono = Mono.justOrEmpty(result.getReturnValue());

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import reactor.core.publisher.Flux;
3535
import reactor.core.publisher.Mono;
3636
import reactor.test.TestSubscriber;
37+
import rx.Completable;
3738
import rx.Observable;
3839

3940
import org.springframework.core.MethodParameter;
@@ -110,6 +111,7 @@ public void useDefaultCharset() throws Exception {
110111
public void voidReturnType() throws Exception {
111112
testVoidReturnType(null, ResolvableType.forType(void.class));
112113
testVoidReturnType(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class));
114+
testVoidReturnType(Completable.complete(), ResolvableType.forClass(Completable.class));
113115
testVoidReturnType(Flux.empty(), ResolvableType.forClassWithGenerics(Flux.class, Void.class));
114116
testVoidReturnType(Observable.empty(), ResolvableType.forClassWithGenerics(Observable.class, Void.class));
115117
}
@@ -269,6 +271,8 @@ void voidReturn() { }
269271

270272
Mono<Void> monoVoid() { return null; }
271273

274+
Completable completable() { return null; }
275+
272276
Flux<Void> fluxVoid() { return null; }
273277

274278
Observable<Void> observableVoid() { return null; }

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.reactivestreams.Publisher;
2929
import reactor.core.publisher.Flux;
3030
import reactor.core.publisher.Mono;
31+
import rx.Completable;
3132
import rx.Observable;
3233
import rx.Single;
3334

@@ -54,9 +55,10 @@
5455
import org.springframework.web.bind.annotation.RestController;
5556
import org.springframework.web.reactive.config.WebReactiveConfiguration;
5657

57-
import static java.util.Arrays.*;
58-
import static org.junit.Assert.*;
59-
import static org.springframework.http.MediaType.*;
58+
import static java.util.Arrays.asList;
59+
import static org.junit.Assert.assertEquals;
60+
import static org.junit.Assert.assertTrue;
61+
import static org.springframework.http.MediaType.APPLICATION_XML;
6062

6163

6264
/**
@@ -233,6 +235,24 @@ public void personCreateWithPublisherXml() throws Exception {
233235
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
234236
}
235237

238+
@Test
239+
public void personCreateWithMono() throws Exception {
240+
ResponseEntity<Void> entity = performPost(
241+
"/person-create/mono", JSON, new Person("Robert"), null, Void.class);
242+
243+
assertEquals(HttpStatus.OK, entity.getStatusCode());
244+
assertEquals(1, getApplicationContext().getBean(PersonCreateController.class).persons.size());
245+
}
246+
247+
@Test
248+
public void personCreateWithSingle() throws Exception {
249+
ResponseEntity<Void> entity = performPost(
250+
"/person-create/single", JSON, new Person("Robert"), null, Void.class);
251+
252+
assertEquals(HttpStatus.OK, entity.getStatusCode());
253+
assertEquals(1, getApplicationContext().getBean(PersonCreateController.class).persons.size());
254+
}
255+
236256
@Test
237257
public void personCreateWithFluxJson() throws Exception {
238258
ResponseEntity<Void> entity = performPost("/person-create/flux", JSON,
@@ -415,18 +435,28 @@ private static class PersonCreateController {
415435
final List<Person> persons = new ArrayList<>();
416436

417437
@PostMapping("/publisher")
418-
public Publisher<Void> createWithPublisher(@RequestBody Publisher<Person> personStream) {
419-
return Flux.from(personStream).doOnNext(persons::add).then();
438+
public Publisher<Void> createWithPublisher(@RequestBody Publisher<Person> publisher) {
439+
return Flux.from(publisher).doOnNext(persons::add).then();
440+
}
441+
442+
@PostMapping("/mono")
443+
public Mono<Void> createWithMono(@RequestBody Mono<Person> mono) {
444+
return mono.doOnNext(persons::add).then();
445+
}
446+
447+
@PostMapping("/single")
448+
public Completable createWithSingle(@RequestBody Single<Person> single) {
449+
return single.map(persons::add).toCompletable();
420450
}
421451

422452
@PostMapping("/flux")
423-
public Mono<Void> createWithFlux(@RequestBody Flux<Person> personStream) {
424-
return personStream.doOnNext(persons::add).then();
453+
public Mono<Void> createWithFlux(@RequestBody Flux<Person> flux) {
454+
return flux.doOnNext(persons::add).then();
425455
}
426456

427457
@PostMapping("/observable")
428-
public Observable<Void> createWithObservable(@RequestBody Observable<Person> personStream) {
429-
return personStream.toList().doOnNext(persons::addAll).flatMap(document -> Observable.empty());
458+
public Observable<Void> createWithObservable(@RequestBody Observable<Person> observable) {
459+
return observable.toList().doOnNext(persons::addAll).flatMap(document -> Observable.empty());
430460
}
431461
}
432462

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.junit.Before;
2525
import org.junit.Test;
2626
import reactor.core.publisher.Mono;
27+
import rx.Completable;
28+
import rx.Single;
2729

2830
import org.springframework.core.codec.ByteBufferEncoder;
2931
import org.springframework.core.codec.CharSequenceEncoder;
@@ -50,7 +52,7 @@
5052
import org.springframework.web.server.adapter.DefaultServerWebExchange;
5153
import org.springframework.web.server.session.MockWebSessionManager;
5254

53-
import static org.junit.Assert.*;
55+
import static org.junit.Assert.assertEquals;
5456

5557

5658
/**
@@ -106,6 +108,9 @@ public void supports() throws NoSuchMethodException {
106108

107109
controller = new TestRestController();
108110
testSupports(controller, "handleToString", true);
111+
testSupports(controller, "handleToMonoString", true);
112+
testSupports(controller, "handleToSingleString", true);
113+
testSupports(controller, "handleToCompletable", true);
109114
testSupports(controller, "handleToResponseEntity", false);
110115
testSupports(controller, "handleToMonoResponseEntity", false);
111116
}
@@ -134,6 +139,18 @@ public String handleToString() {
134139
return null;
135140
}
136141

142+
public Mono<String> handleToMonoString() {
143+
return null;
144+
}
145+
146+
public Single<String> handleToSingleString() {
147+
return null;
148+
}
149+
150+
public Completable handleToCompletable() {
151+
return null;
152+
}
153+
137154
public ResponseEntity<String> handleToResponseEntity() {
138155
return null;
139156
}

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.junit.Test;
2929
import reactor.core.publisher.Mono;
3030
import reactor.test.TestSubscriber;
31+
import rx.Completable;
3132
import rx.Single;
3233

3334
import org.springframework.core.MethodParameter;
@@ -117,8 +118,13 @@ public void supports() throws NoSuchMethodException {
117118
type = forClassWithGenerics(CompletableFuture.class, responseEntity(String.class));
118119
assertTrue(this.resultHandler.supports(handlerResult(value, type)));
119120

121+
// False
122+
120123
type = ResolvableType.forClass(String.class);
121124
assertFalse(this.resultHandler.supports(handlerResult(value, type)));
125+
126+
type = ResolvableType.forClass(Completable.class);
127+
assertFalse(this.resultHandler.supports(handlerResult(value, type)));
122128
}
123129

124130
@Test
@@ -212,6 +218,8 @@ private static class TestController {
212218
CompletableFuture<ResponseEntity<String>> completableFuture() { return null; }
213219

214220
String string() { return null; }
221+
222+
Completable completable() { return null; }
215223
}
216224

217225
}

spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import reactor.core.publisher.Flux;
3434
import reactor.core.publisher.Mono;
3535
import reactor.test.TestSubscriber;
36+
import rx.Completable;
3637
import rx.Single;
3738

3839
import org.springframework.core.MethodParameter;
@@ -98,6 +99,8 @@ public void supports() throws Exception {
9899
testSupports(ResolvableType.forClassWithGenerics(Mono.class, View.class), true);
99100
testSupports(ResolvableType.forClassWithGenerics(Single.class, String.class), true);
100101
testSupports(ResolvableType.forClassWithGenerics(Single.class, View.class), true);
102+
testSupports(ResolvableType.forClassWithGenerics(Mono.class, Void.class), true);
103+
testSupports(ResolvableType.forClass(Completable.class), true);
101104
testSupports(ResolvableType.forClass(Model.class), true);
102105
testSupports(ResolvableType.forClass(Map.class), true);
103106
testSupports(ResolvableType.forClass(TestBean.class), true);
@@ -169,6 +172,8 @@ public void handleWithMultipleResolvers() throws Exception {
169172
public void defaultViewName() throws Exception {
170173
testDefaultViewName(null, ResolvableType.forClass(String.class));
171174
testDefaultViewName(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, String.class));
175+
testDefaultViewName(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class));
176+
testDefaultViewName(Completable.complete(), ResolvableType.forClass(Completable.class));
172177
}
173178

174179
private void testDefaultViewName(Object returnValue, ResolvableType type)
@@ -387,10 +392,14 @@ private static class TestController {
387392

388393
Mono<View> monoView() { return null; }
389394

395+
Mono<Void> monoVoid() { return null; }
396+
390397
Single<String> singleString() { return null; }
391398

392399
Single<View> singleView() { return null; }
393400

401+
Completable completable() { return null; }
402+
394403
Model model() { return null; }
395404

396405
Map map() { return null; }

0 commit comments

Comments
 (0)