diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index aee5abb6e1..62ae5c6621 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -65,7 +65,6 @@ public class ViewResolutionResultHandler implements HandlerResultHandler, Ordere * @param service for converting other reactive types (e.g. rx.Single) to Mono */ public ViewResolutionResultHandler(List resolvers, ConversionService service) { - Assert.notEmpty(resolvers, "At least one ViewResolver is required."); Assert.notNull(service, "'conversionService' is required."); this.viewResolvers.addAll(resolvers); AnnotationAwareOrderComparator.sort(this.viewResolvers); @@ -183,6 +182,14 @@ public class ViewResolutionResultHandler implements HandlerResultHandler, Ordere } } + /** + * Translate the given request into a default view name. This is useful when + * the application leaves the view name unspecified. + *

The default implementation strips the leading and trailing slash from + * the as well as any extension and uses that as the view name. + * @return the default view name to use; if {@code null} is returned + * processing will result in an IllegalStateException. + */ protected String getDefaultViewName(ServerWebExchange exchange, HandlerResult result) { String path = this.pathHelper.getLookupPathForRequest(exchange); if (path.startsWith("/")) { diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java index eff32fb304..05adf2c9f6 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java @@ -59,7 +59,6 @@ import org.springframework.web.server.session.WebSessionManager; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; - /** * Unit tests for {@link ViewResolutionResultHandler}. * @author Rossen Stoyanchev @@ -71,8 +70,6 @@ public class ViewResolutionResultHandlerTests { private MockServerHttpResponse response; - private ServerWebExchange exchange; - private ModelMap model; private DefaultConversionService conversionService; @@ -80,7 +77,6 @@ public class ViewResolutionResultHandlerTests { @Before public void setUp() throws Exception { - this.exchange = createExchange("/path"); this.model = new ExtendedModelMap().addAttribute("id", "123"); this.conversionService = new DefaultConversionService(); this.conversionService.addConverter(new ReactiveStreamsToRxJava1Converter()); @@ -97,6 +93,99 @@ public class ViewResolutionResultHandlerTests { testSupports("handleSingleView", null); } + @Test + public void order() throws Exception { + TestViewResolver resolver1 = new TestViewResolver(); + TestViewResolver resolver2 = new TestViewResolver(); + resolver1.setOrder(2); + resolver2.setOrder(1); + + assertEquals(Arrays.asList(resolver2, resolver1), + new ViewResolutionResultHandler(Arrays.asList(resolver1, resolver2), this.conversionService) + .getViewResolvers()); + } + + @Test + public void viewReference() throws Exception { + Object value = new TestView("account"); + handle("/path", value, ResolvableType.forClass(View.class)); + + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + } + + @Test + public void viewReferenceInMono() throws Exception { + Object value = Mono.just(new TestView("account")); + handle("/path", value, returnTypeFor("handleMonoView")); + + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + } + + @Test + public void viewName() throws Exception { + Object value = "account"; + handle("/path", value, ResolvableType.forClass(String.class), new TestViewResolver("account")); + + TestSubscriber subscriber = new TestSubscriber<>(); + subscriber.bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + } + + @Test + public void viewNameInMono() throws Exception { + Object value = Mono.just("account"); + handle("/path", value, returnTypeFor("handleMonoString"), new TestViewResolver("account")); + + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + } + + @Test + public void viewNameWithMultipleResolvers() throws Exception { + String value = "profile"; + handle("/path", value, ResolvableType.forClass(String.class), + new TestViewResolver("account"), new TestViewResolver("profile")); + + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("profile: {id=123}", asString(buf))); + } + + @Test + public void viewNameUnresolved() throws Exception { + TestSubscriber subscriber = handle("/path", "account", ResolvableType.forClass(String.class)); + + subscriber.assertNoValues(); + } + + @Test + public void viewNameIsNull() throws Exception { + ViewResolver resolver = new TestViewResolver("account"); + + handle("/account", null, ResolvableType.forClass(String.class), resolver); + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + + handle("/account/", null, ResolvableType.forClass(String.class), resolver); + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + + handle("/account.123", null, ResolvableType.forClass(String.class), resolver); + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + } + + @Test + public void viewNameIsEmptyMono() throws Exception { + Object value = Mono.empty(); + handle("/account", value, returnTypeFor("handleMonoString"), new TestViewResolver("account")); + + new TestSubscriber().bindTo(this.response.getBody()) + .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); + } + + private void testSupports(String methodName, Object returnValue) throws NoSuchMethodException { Method method = TestController.class.getMethod(methodName); ResolvableType returnType = ResolvableType.forMethodParameter(method, -1); @@ -106,146 +195,25 @@ public class ViewResolutionResultHandlerTests { assertTrue(handler.supports(result)); } - @Test - public void viewReference() throws Exception { - TestView view = new TestView("account"); - List resolvers = Collections.singletonList(mock(ViewResolver.class)); - ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, this.conversionService); - handle(this.exchange, handler, view, ResolvableType.forClass(View.class)); + private TestSubscriber handle(String path, Object value, ResolvableType type, + ViewResolver... resolvers) throws URISyntaxException { - new TestSubscriber().bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); - } + List resolverList = Arrays.asList(resolvers); + HandlerResultHandler handler = new ViewResolutionResultHandler(resolverList, this.conversionService); + HandlerResult handlerResult = new HandlerResult(new Object(), value, type, this.model); - @Test - public void viewReferenceMono() throws Exception { - TestView view = new TestView("account"); - List resolvers = Collections.singletonList(mock(ViewResolver.class)); - ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, this.conversionService); - handle(this.exchange, handler, Mono.just(view), methodReturnType("handleMonoView")); - - new TestSubscriber().bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); - } - - @Test - public void viewName() throws Exception { - TestView view = new TestView("account"); - TestViewResolver resolver = new TestViewResolver().addView(view); - List resolvers = Collections.singletonList(resolver); - ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, this.conversionService); - handle(this.exchange, handler, "account", ResolvableType.forClass(String.class)); - - TestSubscriber subscriber = new TestSubscriber<>(); - subscriber.bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); - } - - @Test - public void viewNameMono() throws Exception { - TestView view = new TestView("account"); - TestViewResolver resolver = new TestViewResolver().addView(view); - List resolvers = Collections.singletonList(resolver); - ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, this.conversionService); - handle(this.exchange, handler, Mono.just("account"), methodReturnType("handleMonoString")); - - new TestSubscriber().bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); - } - - @Test - public void viewNameWithMultipleResolvers() throws Exception { - TestView view1 = new TestView("account"); - TestView view2 = new TestView("profile"); - TestViewResolver resolver1 = new TestViewResolver().addView(view1); - TestViewResolver resolver2 = new TestViewResolver().addView(view2); - List resolvers = Arrays.asList(resolver1, resolver2); - ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, this.conversionService); - handle(this.exchange, handler, "profile", ResolvableType.forClass(String.class)); - - new TestSubscriber().bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("profile: {id=123}", asString(buf))); - } - - @Test - public void viewNameWithNoMatch() throws Exception { - List resolvers = Collections.singletonList(mock(ViewResolver.class)); - ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, this.conversionService); - TestSubscriber subscriber = handle(this.exchange, handler, "account", ResolvableType.forClass(String.class)); - - subscriber.assertNoValues(); - } - - @Test - public void viewNameNotSpecified() throws Exception { - TestView view = new TestView("account"); - TestViewResolver resolver = new TestViewResolver().addView(view); - List resolvers = Collections.singletonList(resolver); - ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, this.conversionService); - - ServerWebExchange exchange = createExchange("/account"); - handle(exchange, handler, null, ResolvableType.forClass(String.class)); - new TestSubscriber().bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); - - exchange = createExchange("/account/"); - handle(exchange, handler, null, ResolvableType.forClass(String.class)); - new TestSubscriber().bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); - - exchange = createExchange("/account.123"); - handle(exchange, handler, null, ResolvableType.forClass(String.class)); - new TestSubscriber().bindTo(this.response.getBody()) - .assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); - } - - @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 ViewResolutionResultHandler(resolvers, this.conversionService); - ServerWebExchange exchange = createExchange("/account"); - handle(exchange, 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(); - TestViewResolver resolver2 = new TestViewResolver(); - List resolvers = Arrays.asList(resolver1, resolver2); - - resolver1.setOrder(2); - resolver2.setOrder(1); - - ViewResolutionResultHandler resultHandler = - new ViewResolutionResultHandler(resolvers, this.conversionService); - - assertEquals(Arrays.asList(resolver2, resolver1), resultHandler.getViewResolvers()); - } - - - private ServerWebExchange createExchange(String path) throws URISyntaxException { ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI(path)); this.response = new MockServerHttpResponse(); WebSessionManager sessionManager = new DefaultWebSessionManager(); - return new DefaultServerWebExchange(request, this.response, sessionManager); - } + ServerWebExchange exchange = new DefaultServerWebExchange(request, this.response, sessionManager); - private TestSubscriber handle(ServerWebExchange exchange, HandlerResultHandler handler, - Object value, ResolvableType type) { + Mono mono = handler.handleResult(exchange, handlerResult); - HandlerResult result = new HandlerResult(new Object(), value, type, this.model); - Mono mono = handler.handleResult(exchange, result); TestSubscriber subscriber = new TestSubscriber<>(); return subscriber.bindTo(mono).await(Duration.ofSeconds(1)); } - private ResolvableType methodReturnType(String methodName, Class... args) throws NoSuchMethodException { + private ResolvableType returnTypeFor(String methodName, Class... args) throws NoSuchMethodException { Method method = TestController.class.getDeclaredMethod(methodName, args); return ResolvableType.forMethodReturnType(method); } @@ -270,6 +238,10 @@ public class ViewResolutionResultHandlerTests { private int order = Ordered.LOWEST_PRECEDENCE; + public TestViewResolver(String... viewNames) { + Arrays.stream(viewNames).forEach(name -> this.views.put(name, new TestView(name))); + } + public void setOrder(int order) { this.order = order; } @@ -279,11 +251,6 @@ public class ViewResolutionResultHandlerTests { return this.order; } - public TestViewResolver addView(TestView view) { - this.views.put(view.getName(), view); - return this; - } - @Override public Mono resolveViewName(String viewName, Locale locale) { View view = this.views.get(viewName);