From eb9fe235fe1d4731e1f0e0a0aa6b97270724b061 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 26 May 2016 17:34:52 -0400 Subject: [PATCH] Properly handle Mono.empty() for view resolution This commit ensures correct handling for Mono.empty() return value where the declared return type is Mono or Mono. --- .../view/ViewResolverResultHandler.java | 61 +++++++++++++------ .../view/ViewResolverResultHandlerTests.java | 28 ++++++++- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverResultHandler.java index c858401b23..51cfc1d5de 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverResultHandler.java @@ -25,6 +25,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.buffer.DataBuffer; @@ -109,25 +110,29 @@ public class ViewResolverResultHandler implements HandlerResultHandler, Ordered @Override public Mono handleResult(ServerWebExchange exchange, HandlerResult result) { - Mono returnValueMono; - if (this.conversionService.canConvert(result.getReturnValueType().getRawClass(), Mono.class)) { - returnValueMono = this.conversionService.convert(result.getReturnValue().get(), Mono.class); - } - else if (result.getReturnValue().isPresent()) { - returnValueMono = Mono.just(result.getReturnValue().get()); - } - else { - Optional viewName = getDefaultViewName(result, exchange); - if (viewName.isPresent()) { - returnValueMono = Mono.just(viewName.get()); + Mono mono; + ResolvableType elementType; + ResolvableType returnType = result.getReturnValueType(); + + if (this.conversionService.canConvert(returnType.getRawClass(), Mono.class)) { + Optional optionalValue = result.getReturnValue(); + if (optionalValue.isPresent()) { + Mono convertedMono = this.conversionService.convert(optionalValue.get(), Mono.class); + mono = convertedMono.map(o -> o); } else { - returnValueMono = Mono.error(new IllegalStateException("Handler [" + result.getHandler() + "] " + - "neither returned a view name nor a View object")); + mono = Mono.empty(); } + elementType = returnType.getGeneric(0); + } + else { + mono = Mono.justOrEmpty(result.getReturnValue()); + elementType = returnType; } - return returnValueMono.then(returnValue -> { + mono = mono.otherwiseIfEmpty(handleMissingReturnValue(exchange, result, elementType)); + + return mono.then(returnValue -> { if (returnValue instanceof View) { Flux body = ((View) returnValue).render(result, null, exchange); return exchange.getResponse().setBody(body); @@ -144,15 +149,33 @@ public class ViewResolverResultHandler implements HandlerResultHandler, Ordered }); } else { - // Should not happen - return Mono.error(new IllegalStateException( - "Unexpected return value: " + returnValue.getClass())); + // Eventually for model-related return values (should not happen now) + return Mono.error(new IllegalStateException("Unexpected return value")); } }); } - protected Optional getDefaultViewName(HandlerResult result, ServerWebExchange exchange) { - return Optional.empty(); + private Mono handleMissingReturnValue(ServerWebExchange exchange, HandlerResult result, + ResolvableType elementType) { + + if (isStringOrViewReference(elementType.getRawClass())) { + String defaultViewName = getDefaultViewName(exchange, result); + if (defaultViewName != null) { + return Mono.just(defaultViewName); + } + else { + return Mono.error(new IllegalStateException("Handler [" + result.getHandler() + "] " + + "neither returned a view name nor a View object")); + } + } + else { + // Eventually for model-related return values (should not happen now) + return Mono.error(new IllegalStateException("Unexpected return value type")); + } + } + + protected String getDefaultViewName(ServerWebExchange exchange, HandlerResult result) { + return null; } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolverResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolverResultHandlerTests.java index 23c6a1eaf8..1da4de85a9 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolverResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolverResultHandlerTests.java @@ -26,7 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -128,7 +127,7 @@ public class ViewResolverResultHandlerTests { TestView view = new TestView("account"); List resolvers = Collections.singletonList(mock(ViewResolver.class)); ViewResolverResultHandler handler = new ViewResolverResultHandler(resolvers, this.conversionService); - handle(handler, Mono.just(view), ResolvableType.forClass(Mono.class)); + handle(handler, Mono.just(view), methodReturnType("handleMonoView")); new TestSubscriber().bindTo(this.response.getBody()) .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); @@ -153,7 +152,7 @@ public class ViewResolverResultHandlerTests { TestViewResolver resolver = new TestViewResolver().addView(view); List resolvers = Collections.singletonList(resolver); ViewResolverResultHandler handler = new ViewResolverResultHandler(resolvers, this.conversionService); - handle(handler, Mono.just("account"), ResolvableType.forClass(Mono.class)); + handle(handler, Mono.just("account"), methodReturnType("handleMonoString")); new TestSubscriber().bindTo(this.response.getBody()) .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); @@ -192,6 +191,24 @@ public class ViewResolverResultHandlerTests { assertThat(ex.getMessage(), endsWith("neither returned a view name nor a View object"))); } + @Test + public void viewNameMonoEmpty() throws Exception { + TestView view = new TestView("account"); + TestViewResolver resolver = new TestViewResolver().addView(view); + List resolvers = Collections.singletonList(resolver); + HandlerResultHandler handler = new ViewResolverResultHandler(resolvers, this.conversionService) { + @Override + protected String getDefaultViewName(ServerWebExchange exchange, HandlerResult result) { + return "account"; + } + }; + handle(handler, Mono.empty(), methodReturnType("handleMonoString")); + + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + } + + @Test public void ordered() throws Exception { TestViewResolver resolver1 = new TestViewResolver(); @@ -216,6 +233,11 @@ public class ViewResolverResultHandlerTests { return subscriber.bindTo(mono).await(Duration.ofSeconds(1)); } + private ResolvableType methodReturnType(String methodName, Class... args) throws NoSuchMethodException { + Method method = TestController.class.getDeclaredMethod(methodName, args); + return ResolvableType.forMethodReturnType(method); + } + private static DataBuffer asDataBuffer(String value) { ByteBuffer byteBuffer = ByteBuffer.wrap(value.getBytes(UTF_8)); return new DefaultDataBufferAllocator().wrap(byteBuffer);