Browse Source

Properly handle Mono.empty() for view resolution

This commit ensures correct handling for Mono.empty() return value
where the declared return type is Mono<String> or Mono<View>.
pull/1111/head
Rossen Stoyanchev 9 years ago
parent
commit
eb9fe235fe
  1. 61
      spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolverResultHandler.java
  2. 28
      spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolverResultHandlerTests.java

61
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 reactor.core.publisher.Mono;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
@ -109,25 +110,29 @@ public class ViewResolverResultHandler implements HandlerResultHandler, Ordered
@Override @Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
Mono<?> returnValueMono; Mono<Object> mono;
if (this.conversionService.canConvert(result.getReturnValueType().getRawClass(), Mono.class)) { ResolvableType elementType;
returnValueMono = this.conversionService.convert(result.getReturnValue().get(), Mono.class); ResolvableType returnType = result.getReturnValueType();
}
else if (result.getReturnValue().isPresent()) { if (this.conversionService.canConvert(returnType.getRawClass(), Mono.class)) {
returnValueMono = Mono.just(result.getReturnValue().get()); Optional<Object> optionalValue = result.getReturnValue();
} if (optionalValue.isPresent()) {
else { Mono<?> convertedMono = this.conversionService.convert(optionalValue.get(), Mono.class);
Optional<String> viewName = getDefaultViewName(result, exchange); mono = convertedMono.map(o -> o);
if (viewName.isPresent()) {
returnValueMono = Mono.just(viewName.get());
} }
else { else {
returnValueMono = Mono.error(new IllegalStateException("Handler [" + result.getHandler() + "] " + mono = Mono.empty();
"neither returned a view name nor a View object"));
} }
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) { if (returnValue instanceof View) {
Flux<DataBuffer> body = ((View) returnValue).render(result, null, exchange); Flux<DataBuffer> body = ((View) returnValue).render(result, null, exchange);
return exchange.getResponse().setBody(body); return exchange.getResponse().setBody(body);
@ -144,15 +149,33 @@ public class ViewResolverResultHandler implements HandlerResultHandler, Ordered
}); });
} }
else { else {
// Should not happen // Eventually for model-related return values (should not happen now)
return Mono.error(new IllegalStateException( return Mono.error(new IllegalStateException("Unexpected return value"));
"Unexpected return value: " + returnValue.getClass()));
} }
}); });
} }
protected Optional<String> getDefaultViewName(HandlerResult result, ServerWebExchange exchange) { private Mono<Object> handleMissingReturnValue(ServerWebExchange exchange, HandlerResult result,
return Optional.empty(); 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;
} }
} }

28
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.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -128,7 +127,7 @@ public class ViewResolverResultHandlerTests {
TestView view = new TestView("account"); TestView view = new TestView("account");
List<ViewResolver> resolvers = Collections.singletonList(mock(ViewResolver.class)); List<ViewResolver> resolvers = Collections.singletonList(mock(ViewResolver.class));
ViewResolverResultHandler handler = new ViewResolverResultHandler(resolvers, this.conversionService); 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<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf)));
@ -153,7 +152,7 @@ public class ViewResolverResultHandlerTests {
TestViewResolver resolver = new TestViewResolver().addView(view); TestViewResolver resolver = new TestViewResolver().addView(view);
List<ViewResolver> resolvers = Collections.singletonList(resolver); List<ViewResolver> resolvers = Collections.singletonList(resolver);
ViewResolverResultHandler handler = new ViewResolverResultHandler(resolvers, this.conversionService); 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<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .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"))); 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<ViewResolver> 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<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf)));
}
@Test @Test
public void ordered() throws Exception { public void ordered() throws Exception {
TestViewResolver resolver1 = new TestViewResolver(); TestViewResolver resolver1 = new TestViewResolver();
@ -216,6 +233,11 @@ public class ViewResolverResultHandlerTests {
return subscriber.bindTo(mono).await(Duration.ofSeconds(1)); 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) { private static DataBuffer asDataBuffer(String value) {
ByteBuffer byteBuffer = ByteBuffer.wrap(value.getBytes(UTF_8)); ByteBuffer byteBuffer = ByteBuffer.wrap(value.getBytes(UTF_8));
return new DefaultDataBufferAllocator().wrap(byteBuffer); return new DefaultDataBufferAllocator().wrap(byteBuffer);

Loading…
Cancel
Save