From 1ad22b922f9e42a382c7d27a2843b9745d263a35 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 17 Jul 2014 22:55:09 -0400 Subject: [PATCH] Enhance view controller MVC config This change adds support for configuring redirect view controllers and also status controllers to the MVC Java config and the MVC namespace. Issue: SPR-11543 --- .../servlet/config/MvcNamespaceHandler.java | 2 + .../ViewControllerBeanDefinitionParser.java | 73 ++++++++++-- .../RedirectViewControllerRegistration.java | 93 ++++++++++++++++ .../ViewControllerRegistration.java | 37 ++++--- .../annotation/ViewControllerRegistry.java | 44 ++++++-- .../config/annotation/WebMvcConfigurer.java | 7 +- .../web/servlet/config/spring-mvc-4.1.xsd | 104 ++++++++++++++++-- .../web/servlet/config/MvcNamespaceTests.java | 56 ++++++++-- .../ViewControllerRegistryTests.java | 78 ++++++++++++- ...MvcConfigurationSupportExtensionTests.java | 21 ++-- .../mvc-config-view-controllers-minimal.xml | 10 +- .../config/mvc-config-view-controllers.xml | 11 +- 12 files changed, 466 insertions(+), 70 deletions(-) create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/RedirectViewControllerRegistration.java diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java index 017603a91a..69cced3500 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java @@ -35,6 +35,8 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser()); registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser()); registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser()); + registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser()); + registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser()); registerBeanDefinitionParser("tiles", new TilesBeanDefinitionParser()); registerBeanDefinitionParser("freemarker", new FreeMarkerBeanDefinitionParser()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java index 1161c85cdd..a145684a44 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java @@ -19,19 +19,32 @@ package org.springframework.web.servlet.config; import java.util.Map; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.mvc.ParameterizableViewController; +import org.springframework.web.servlet.view.RedirectView; import org.w3c.dom.Element; /** - * {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a - * {@code view-controller} element to register a {@link ParameterizableViewController}. - * Will also register a {@link SimpleUrlHandlerMapping} for view controllers. + * {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that + * parses the following MVC namespace elements: + * + * + *

All elements result in the registration of a + * {@link org.springframework.web.servlet.mvc.ParameterizableViewController + * ParameterizableViewController} with all controllers mapped using in a single + * {@link org.springframework.web.servlet.handler.SimpleUrlHandlerMapping + * SimpleUrlHandlerMapping}. * * @author Keith Donald * @author Christian Dupuis @@ -50,7 +63,7 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser { Object source = parserContext.extractSource(element); // Register SimpleUrlHandlerMapping for view controllers - BeanDefinition handlerMapping = registerHandlerMapping(parserContext, source); + BeanDefinition hm = registerHandlerMapping(parserContext, source); // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off" MvcNamespaceUtils.registerDefaultComponents(parserContext, source); @@ -58,16 +71,41 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser { // Create view controller bean definition RootBeanDefinition controller = new RootBeanDefinition(ParameterizableViewController.class); controller.setSource(source); - if (element.hasAttribute("view-name")) { - controller.getPropertyValues().add("viewName", element.getAttribute("view-name")); + + HttpStatus statusCode = null; + if (element.hasAttribute("status-code")) { + int statusValue = Integer.valueOf(element.getAttribute("status-code")); + statusCode = HttpStatus.valueOf(statusValue); + } + + String name = element.getLocalName(); + if (name.equals("view-controller")) { + if (element.hasAttribute("view-name")) { + controller.getPropertyValues().add("viewName", element.getAttribute("view-name")); + } + if (statusCode != null) { + controller.getPropertyValues().add("statusCode", statusCode); + } + } + else if (name.equals("redirect-view-controller")) { + controller.getPropertyValues().add("view", getRedirectView(element, statusCode, source)); + } + else if (name.equals("status-controller")) { + controller.getPropertyValues().add("statusCode", statusCode); + controller.getPropertyValues().add("statusOnly", true); + } + else { + // Should never happen... + throw new IllegalStateException("Unexpected tag name: " + name); } + Map urlMap; - if (handlerMapping.getPropertyValues().contains("urlMap")) { - urlMap = (Map) handlerMapping.getPropertyValues().getPropertyValue("urlMap").getValue(); + if (hm.getPropertyValues().contains("urlMap")) { + urlMap = (Map) hm.getPropertyValues().getPropertyValue("urlMap").getValue(); } else { urlMap = new ManagedMap(); - handlerMapping.getPropertyValues().add("urlMap", urlMap); + hm.getPropertyValues().add("urlMap", urlMap); } urlMap.put(element.getAttribute("path"), controller); @@ -91,4 +129,21 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser { return beanDef; } + private RootBeanDefinition getRedirectView(Element element, HttpStatus status, Object source) { + ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + cavs.addIndexedArgumentValue(0, element.getAttribute("redirect-url")); + RootBeanDefinition redirectView = new RootBeanDefinition(RedirectView.class, cavs, null); + redirectView.setSource(source); + if (status != null) { + redirectView.getPropertyValues().add("statusCode", status); + } + if (element.hasAttribute("context-relative")) { + redirectView.getPropertyValues().add("contextRelative", element.getAttribute("context-relative")); + } + if (element.hasAttribute("keep-query-params")) { + redirectView.getPropertyValues().add("propagateQueryParams", element.getAttribute("keep-query-params")); + } + return redirectView; + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/RedirectViewControllerRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/RedirectViewControllerRegistration.java new file mode 100644 index 0000000000..6bf1a8163c --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/RedirectViewControllerRegistration.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.config.annotation; + +import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; +import org.springframework.web.servlet.mvc.ParameterizableViewController; +import org.springframework.web.servlet.view.RedirectView; + +/** + * Assist with the registration of a single redirect view controller. + * + * @author Rossen Stoyanchev + * @since 4.1 + */ +public class RedirectViewControllerRegistration { + + private final String urlPath; + + private final RedirectView redirectView; + + private final ParameterizableViewController controller = new ParameterizableViewController(); + + + public RedirectViewControllerRegistration(String urlPath, String redirectUrl) { + Assert.notNull(urlPath, "'urlPath' is required."); + Assert.notNull(redirectUrl, "'redirectUrl' is required."); + this.urlPath = urlPath; + this.redirectView = new RedirectView(redirectUrl); + this.redirectView.setContextRelative(true); + this.controller.setView(this.redirectView); + } + + + /** + * Set the specific redirect 3xx status code to use. + * + *

If not set, {@link org.springframework.web.servlet.view.RedirectView} + * will select {@code HttpStatus.MOVED_TEMPORARILY (302)} by default. + */ + public RedirectViewControllerRegistration setStatusCode(HttpStatus statusCode) { + Assert.isTrue(statusCode.is3xxRedirection(), "Not a redirect status code."); + this.redirectView.setStatusCode(statusCode); + return this; + } + + /** + * Whether to interpret a given redirect URL that starts with a slash ("/") + * as relative to the current ServletContext, i.e. as relative to the web + * application root. + * + *

Default is {@code true}. + */ + public RedirectViewControllerRegistration setContextRelative(boolean contextRelative) { + this.redirectView.setContextRelative(contextRelative); + return this; + } + + /** + * Whether to propagate the query parameters of the current request through + * to the target redirect URL. + * + *

Default is {@code false}. + */ + public RedirectViewControllerRegistration setKeepQueryParams(boolean propagate) { + this.redirectView.setPropagateQueryParams(propagate); + return this; + } + + + protected String getUrlPath() { + return this.urlPath; + } + + protected ParameterizableViewController getViewController() { + return this.controller; + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistration.java index 5b1f069441..eb486d9582 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistration.java @@ -16,6 +16,7 @@ package org.springframework.web.servlet.config.annotation; +import org.springframework.http.HttpStatus; import org.springframework.util.Assert; import org.springframework.web.servlet.RequestToViewNameTranslator; import org.springframework.web.servlet.mvc.ParameterizableViewController; @@ -31,12 +32,9 @@ public class ViewControllerRegistration { private final String urlPath; - private String viewName; + private final ParameterizableViewController controller = new ParameterizableViewController(); - /** - * Creates a registration for the given URL path (or path pattern). - */ public ViewControllerRegistration(String urlPath) { Assert.notNull(urlPath, "'urlPath' is required."); this.urlPath = urlPath; @@ -44,17 +42,28 @@ public class ViewControllerRegistration { /** - * Set the view name to return. + * Set the status code to set on the response. Optional. + * + *

If not set the response status will be 200 (OK). + */ + public ViewControllerRegistration setStatusCode(HttpStatus statusCode) { + this.controller.setStatusCode(statusCode); + return this; + } + + /** + * Set the view name to return. Optional. * - *

If not specified, the view controller returns {@code null} as the view - * name in which case the configured {@link RequestToViewNameTranslator} - * selects the view. In effect {@code DefaultRequestToViewNameTranslator} - * translates "/foo/bar" to "foo/bar". + *

If not specified, the view controller will return {@code null} as the + * view name in which case the configured {@link RequestToViewNameTranslator} + * will select the view name. The {@code DefaultRequestToViewNameTranslator} + * for example translates "/foo/bar" to "foo/bar". * * @see org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator */ - public void setViewName(String viewName) { - this.viewName = viewName; + public ViewControllerRegistration setViewName(String viewName) { + this.controller.setViewName(viewName); + return this; } @@ -62,10 +71,8 @@ public class ViewControllerRegistration { return this.urlPath; } - protected Object getViewController() { - ParameterizableViewController controller = new ParameterizableViewController(); - controller.setViewName(this.viewName); - return controller; + protected ParameterizableViewController getViewController() { + return this.controller; } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistry.java index 11535f7c00..8b7fc6073c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistry.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistry.java @@ -21,14 +21,13 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.springframework.web.servlet.HandlerMapping; +import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.AbstractHandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; /** - * Enables the registration of view controllers that have no logic other than to - * return the view name they're configured with. This is an alternative to - * writing a controller manually to do the same. + * Assists with the registration of simple automated controllers pre-configured + * with status code and/or a view. * * @author Rossen Stoyanchev * @author Keith Donald @@ -36,13 +35,17 @@ import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; */ public class ViewControllerRegistry { - private final List registrations = new ArrayList(); + private final List registrations = new ArrayList(4); + + private final List redirectRegistrations = + new ArrayList(10); private int order = 1; /** - * Register a view controller mapped to the given URL path or URL path pattern. + * Map a view controller to the given URL path (or pattern) in order to render + * a response with a pre-configured status code and view. */ public ViewControllerRegistration addViewController(String urlPath) { ViewControllerRegistration registration = new ViewControllerRegistration(urlPath); @@ -50,6 +53,30 @@ public class ViewControllerRegistry { return registration; } + /** + * Map a view controller to the given URL path (or pattern) in order to redirect + * to another URL. By default the redirect URL is expected to be relative to + * the current ServletContext, i.e. as relative to the web application root. + * @since 4.1 + */ + public RedirectViewControllerRegistration addRedirectViewController(String urlPath, String redirectUrl) { + RedirectViewControllerRegistration registration = new RedirectViewControllerRegistration(urlPath, redirectUrl); + this.redirectRegistrations.add(registration); + return registration; + } + + /** + * Map a simple controller to the given URL path (or pattern) in order to + * set the response status to the given code without rendering a body. + * @since 4.1 + */ + public void addStatusController(String urlPath, HttpStatus statusCode) { + ViewControllerRegistration registration = new ViewControllerRegistration(urlPath); + registration.setStatusCode(statusCode); + registration.getViewController().setStatusOnly(true); + this.registrations.add(registration); + } + /** * Specify the order to use for the {@code HandlerMapping} used to map view * controllers relative to other handler mappings configured in Spring MVC. @@ -66,13 +93,16 @@ public class ViewControllerRegistry { * controller mappings, or {@code null} for no registrations. */ protected AbstractHandlerMapping getHandlerMapping() { - if (this.registrations.isEmpty()) { + if (this.registrations.isEmpty() && this.redirectRegistrations.isEmpty()) { return null; } Map urlMap = new LinkedHashMap(); for (ViewControllerRegistration registration : this.registrations) { urlMap.put(registration.getUrlPath(), registration.getViewController()); } + for (RedirectViewControllerRegistration registration : this.redirectRegistrations) { + urlMap.put(registration.getUrlPath(), registration.getViewController()); + } SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); handlerMapping.setOrder(this.order); handlerMapping.setUrlMap(urlMap); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java index 7f6021581f..3c4c120104 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java @@ -133,8 +133,11 @@ public interface WebMvcConfigurer { MessageCodesResolver getMessageCodesResolver(); /** - * Add view controllers to create a direct mapping between a URL path and - * view name without the need for a controller in between. + * Configure simple automated controllers pre-configured with the response + * status code and/or a view to render the response body. This is useful in + * cases where there is no need for custom controller logic -- e.g. render a + * home page, perform simple site URL redirects, return a 404 status with + * HTML content, a 204 with no content, and more. */ void addViewControllers(ViewControllerRegistry registry); diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd index 6d2758ae77..be54f0ca3a 100644 --- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd +++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd @@ -574,27 +574,115 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index d08724a344..0a9d7393e4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -37,6 +37,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.format.support.FormattingConversionServiceFactoryBean; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.web.test.MockHttpServletRequest; @@ -54,6 +55,7 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.accept.*; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.async.CallableProcessingInterceptor; @@ -61,6 +63,7 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter; import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.method.support.InvocableHandlerMethod; @@ -118,10 +121,14 @@ public class MvcNamespaceTests { @Before public void setUp() throws Exception { + TestMockServletContext servletContext = new TestMockServletContext(); appContext = new GenericWebApplicationContext(); - appContext.setServletContext(new TestMockServletContext()); + appContext.setServletContext(servletContext); LocaleContextHolder.setLocale(Locale.US); + String attributeName = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE; + appContext.getServletContext().setAttribute(attributeName, appContext); + handler = new TestController(); Method method = TestController.class.getMethod("testBind", Date.class, TestBean.class, BindingResult.class); handlerMethod = new InvocableHandlerMethod(handler, method); @@ -443,7 +450,7 @@ public class MvcNamespaceTests { SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class); assertNotNull(adapter); - request.setRequestURI("/foo"); + request = new MockHttpServletRequest("GET", "/foo"); chain = mapping2.getHandler(request); assertEquals(4, chain.getInterceptors().length); assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor); @@ -452,7 +459,7 @@ public class MvcNamespaceTests { ModelAndView mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler()); assertNull(mv.getViewName()); - request.setRequestURI("/myapp/app/bar"); + request = new MockHttpServletRequest("GET", "/myapp/app/bar"); request.setContextPath("/myapp"); request.setServletPath("/app"); chain = mapping2.getHandler(request); @@ -460,10 +467,10 @@ public class MvcNamespaceTests { assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor); assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor); assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor); - ModelAndView mv2 = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler()); - assertEquals("baz", mv2.getViewName()); + mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler()); + assertEquals("baz", mv.getViewName()); - request.setRequestURI("/myapp/app/"); + request = new MockHttpServletRequest("GET", "/myapp/app/"); request.setContextPath("/myapp"); request.setServletPath("/app"); chain = mapping2.getHandler(request); @@ -471,8 +478,29 @@ public class MvcNamespaceTests { assertTrue(chain.getInterceptors()[1] instanceof ConversionServiceExposingInterceptor); assertTrue(chain.getInterceptors()[2] instanceof LocaleChangeInterceptor); assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor); - ModelAndView mv3 = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler()); - assertEquals("root", mv3.getViewName()); + mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler()); + assertEquals("root", mv.getViewName()); + + request = new MockHttpServletRequest("GET", "/myapp/app/old"); + request.setContextPath("/myapp"); + request.setServletPath("/app"); + request.setQueryString("a=b"); + chain = mapping2.getHandler(request); + mv = adapter.handle(request, new MockHttpServletResponse(), chain.getHandler()); + assertNotNull(mv.getView()); + assertEquals(RedirectView.class, mv.getView().getClass()); + RedirectView redirectView = (RedirectView) mv.getView(); + MockHttpServletResponse response = new MockHttpServletResponse(); + redirectView.render(Collections.emptyMap(), request, response); + assertEquals("/new?a=b", response.getRedirectedUrl()); + assertEquals(308, response.getStatus()); + + request = new MockHttpServletRequest("GET", "/bad"); + chain = mapping2.getHandler(request); + response = new MockHttpServletResponse(); + mv = adapter.handle(request, response, chain.getHandler()); + assertNull(mv); + assertEquals(404, response.getStatus()); } /** WebSphere gives trailing servlet path slashes by default!! */ @@ -524,7 +552,13 @@ public class MvcNamespaceTests { public void testViewControllersDefaultConfig() { loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 6); - BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class); + SimpleUrlHandlerMapping hm = this.appContext.getBean(SimpleUrlHandlerMapping.class); + assertNotNull(hm); + assertNotNull(hm.getUrlMap().get("/path")); + assertNotNull(hm.getUrlMap().get("/old")); + assertNotNull(hm.getUrlMap().get("/bad")); + + BeanNameUrlHandlerMapping beanNameMapping = this.appContext.getBean(BeanNameUrlHandlerMapping.class); assertNotNull(beanNameMapping); assertEquals(2, beanNameMapping.getOrder()); } @@ -683,8 +717,8 @@ public class MvcNamespaceTests { XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext); ClassPathResource resource = new ClassPathResource(fileName, AnnotationDrivenBeanDefinitionParserTests.class); reader.loadBeanDefinitions(resource); - assertEquals("Bean names: " + Arrays.toString(this.appContext.getBeanDefinitionNames()), - expectedBeanCount, appContext.getBeanDefinitionCount()); + String names = Arrays.toString(this.appContext.getBeanDefinitionNames()); + assertEquals("Bean names: " + names, expectedBeanCount, appContext.getBeanDefinitionCount()); appContext.refresh(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistryTests.java index cb76073a98..ebc7736f3f 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewControllerRegistryTests.java @@ -17,15 +17,22 @@ package org.springframework.web.servlet.config.annotation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import java.util.Collections; import java.util.Map; import org.junit.Before; import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.test.MockHttpServletRequest; +import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.mvc.ParameterizableViewController; +import org.springframework.web.servlet.view.RedirectView; /** * Test fixture with a {@link ViewControllerRegistry}. @@ -36,10 +43,16 @@ public class ViewControllerRegistryTests { private ViewControllerRegistry registry; + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + @Before public void setUp() { this.registry = new ViewControllerRegistry(); + this.request = new MockHttpServletRequest("GET", "/"); + this.response = new MockHttpServletResponse(); } @Test @@ -50,21 +63,59 @@ public class ViewControllerRegistryTests { @Test public void addViewController() { this.registry.addViewController("/path").setViewName("viewName"); - Map urlMap = getHandlerMapping().getUrlMap(); - ParameterizableViewController controller = (ParameterizableViewController) urlMap.get("/path"); - assertNotNull(controller); + ParameterizableViewController controller = getController("/path"); assertEquals("viewName", controller.getViewName()); + assertNull(controller.getStatusCode()); + assertFalse(controller.isStatusOnly()); } @Test public void addViewControllerWithDefaultViewName() { this.registry.addViewController("/path"); - Map urlMap = getHandlerMapping().getUrlMap(); - ParameterizableViewController controller = (ParameterizableViewController) urlMap.get("/path"); - assertNotNull(controller); + ParameterizableViewController controller = getController("/path"); + assertNull(controller.getViewName()); + assertNull(controller.getStatusCode()); + assertFalse(controller.isStatusOnly()); + } + + @Test + public void addRedirectViewController() throws Exception { + this.registry.addRedirectViewController("/path", "/redirectTo"); + RedirectView redirectView = getRedirectView("/path"); + this.request.setQueryString("a=b"); + this.request.setContextPath("/context"); + redirectView.render(Collections.emptyMap(), this.request, this.response); + + assertEquals(302, this.response.getStatus()); + assertEquals("/context/redirectTo", this.response.getRedirectedUrl()); + } + + @Test + public void addRedirectViewControllerWithCustomSettings() throws Exception { + this.registry.addRedirectViewController("/path", "/redirectTo") + .setContextRelative(false) + .setKeepQueryParams(true) + .setStatusCode(HttpStatus.PERMANENT_REDIRECT); + + RedirectView redirectView = getRedirectView("/path"); + this.request.setQueryString("a=b"); + this.request.setContextPath("/context"); + redirectView.render(Collections.emptyMap(), this.request, this.response); + + assertEquals(308, this.response.getStatus()); + assertEquals("/redirectTo?a=b", response.getRedirectedUrl()); + } + + @Test + public void addStatusController() { + this.registry.addStatusController("/path", HttpStatus.NOT_FOUND); + ParameterizableViewController controller = getController("/path"); assertNull(controller.getViewName()); + assertEquals(HttpStatus.NOT_FOUND, controller.getStatusCode()); + assertTrue(controller.isStatusOnly()); } + @Test public void order() { this.registry.addViewController("/path"); @@ -76,9 +127,24 @@ public class ViewControllerRegistryTests { assertEquals(2, handlerMapping.getOrder()); } + private ParameterizableViewController getController(String path) { + Map urlMap = getHandlerMapping().getUrlMap(); + ParameterizableViewController controller = (ParameterizableViewController) urlMap.get(path); + assertNotNull(controller); + return controller; + } private SimpleUrlHandlerMapping getHandlerMapping() { return (SimpleUrlHandlerMapping) this.registry.getHandlerMapping(); } + private RedirectView getRedirectView(String path) { + ParameterizableViewController controller = getController("/path"); + assertNull(controller.getViewName()); + assertNotNull(controller.getView()); + assertEquals(RedirectView.class, controller.getView().getClass()); + return (RedirectView) controller.getView(); + } + + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java index 17283da269..7eca7a9281 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java @@ -26,6 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; import org.springframework.core.Ordered; +import org.springframework.http.HttpStatus; import org.springframework.tests.sample.beans.TestBean; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.FileSystemResourceLoader; @@ -121,8 +122,12 @@ public class WebMvcConfigurationSupportExtensionTests { assertEquals(1, handlerMapping.getOrder()); assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass()); assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass()); - HandlerExecutionChain handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/path")); - assertNotNull(handler.getHandler()); + chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/path")); + assertNotNull(chain.getHandler()); + chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/bad")); + assertNotNull(chain.getHandler()); + chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/old")); + assertNotNull(chain.getHandler()); handlerMapping = (AbstractHandlerMapping) this.config.resourceHandlerMapping(); handlerMapping.setApplicationContext(this.context); @@ -130,15 +135,15 @@ public class WebMvcConfigurationSupportExtensionTests { assertEquals(Integer.MAX_VALUE - 1, handlerMapping.getOrder()); assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass()); assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass()); - handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif")); - assertNotNull(handler.getHandler()); + chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif")); + assertNotNull(chain.getHandler()); handlerMapping = (AbstractHandlerMapping) this.config.defaultServletHandlerMapping(); handlerMapping.setApplicationContext(this.context); assertNotNull(handlerMapping); assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder()); - handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/anyPath")); - assertNotNull(handler.getHandler()); + chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/anyPath")); + assertNotNull(chain.getHandler()); } @SuppressWarnings("unchecked") @@ -347,7 +352,9 @@ public class WebMvcConfigurationSupportExtensionTests { @Override public void addViewControllers(ViewControllerRegistry registry) { - registry.addViewController("/path"); + registry.addViewController("/path").setViewName("view"); + registry.addRedirectViewController("/old", "/new").setStatusCode(HttpStatus.PERMANENT_REDIRECT); + registry.addStatusController("/bad", HttpStatus.NOT_FOUND); } @Override diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml index fdfe28ff68..130921946d 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers-minimal.xml @@ -2,9 +2,15 @@ - + + + + + + diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml index fe2f06d31e..2f9c585115 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-controllers.xml @@ -2,17 +2,22 @@ + - - + + + +