diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java index c5493d0ec64..db984948fb4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * 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. @@ -19,82 +19,148 @@ package org.springframework.web.servlet.mvc; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.View; import org.springframework.web.servlet.support.RequestContextUtils; /** - *
Trivial controller that always returns a named view. The view - * can be configured using an exposed configuration property. This - * controller offers an alternative to sending a request straight to a view - * such as a JSP. The advantage here is that the client is not exposed to - * the concrete view technology but rather just to the controller URL; - * the concrete view will be determined by the ViewResolver. - * - *
An alternative to the ParameterizableViewController is a - * {@link org.springframework.web.servlet.mvc.multiaction.MultiActionController MultiActionController}, - * which can define a variety of handler methods that just return a plain - * ModelAndView instance for a given view name. - * - *
Workflow
- * (and that defined by superclass):
- *
Exposed configuration properties
- * (and those defined by superclass):
- *
| name | - *default | - *description | - *
| viewName | - *null | - *the name of the view the viewResolver will use to forward to - * (if this property is not set, a null view name will be returned - * directing the caller to calculate the view name from the current request) | - *
When a "redirect:" prefixed view name is configured, there is no need + * to set this property since RedirectView will do that. However this property + * may still be used to override the 3xx status code of {@code RedirectView}. + * For full control over redirecting provide a {@code RedirectView} instance. + * + *
If the status code is 204 and no view is configured, the request is + * fully handled within the controller. + * + * @since 4.1 + */ + public void setStatusCode(HttpStatus statusCode) { + this.statusCode = statusCode; + } + + /** + * Return the configured HTTP status code or {@code null}. + * @since 4.1 + */ + public HttpStatus getStatusCode() { + return this.statusCode; + } + + + /** + * The property can be used to indicate the request is considered fully + * handled within the controller and that no view should be used for rendering. + * Useful in combination with {@link #setStatusCode}. + *
By default this is set to {@code false}. + * @since 4.1 + */ + public void setStatusOnly(boolean statusOnly) { + this.statusOnly = statusOnly; + } + + /** + * Whether the request is fully handled within the controller. + */ + public boolean isStatusOnly() { + return this.statusOnly; } /** * Return a ModelAndView object with the specified view name. - * The content of {@link RequestContextUtils#getInputFlashMap} is also added to the model. + * + *
The content of the {@link RequestContextUtils#getInputFlashMap + * "input" FlashMap} is also added to the model. + * * @see #getViewName() */ @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { - return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request)); + + String viewName = getViewName(); + + if (getStatusCode() != null) { + if (getStatusCode().is3xxRedirection()) { + request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, getStatusCode()); + viewName = (viewName != null && !viewName.startsWith("redirect:") ? "redirect:" + viewName : viewName); + } + else { + response.setStatus(getStatusCode().value()); + if (isStatusOnly() || (getStatusCode().equals(HttpStatus.NO_CONTENT) && getViewName() == null)) { + return null; + } + } + } + + ModelAndView modelAndView = new ModelAndView(); + modelAndView.addAllObjects(RequestContextUtils.getInputFlashMap(request)); + + if (getViewName() != null) { + modelAndView.setViewName(viewName); + } + else { + modelAndView.setView(getView()); + } + + return (isStatusOnly() ? null : modelAndView); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/ParameterizableViewControllerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/ParameterizableViewControllerTests.java new file mode 100644 index 00000000000..190e7b264bb --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/ParameterizableViewControllerTests.java @@ -0,0 +1,126 @@ +/* + * 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.mvc.support; + +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.ModelAndView; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.mvc.ParameterizableViewController; +import org.springframework.web.servlet.view.RedirectView; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * Unit tests for + * {@link org.springframework.web.servlet.mvc.ParameterizableViewController}. + * + * @author Rossen Stoyanchev + * @since 4.1 + */ +public class ParameterizableViewControllerTests { + + private ParameterizableViewController controller; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + + @Before + public void setUp() throws Exception { + this.controller = new ParameterizableViewController(); + this.request = new MockHttpServletRequest("GET", "/"); + this.response = new MockHttpServletResponse(); + } + + + @Test + public void defaultViewName() throws Exception { + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + assertNull(modelAndView.getViewName()); + } + + @Test + public void viewName() throws Exception { + this.controller.setViewName("view"); + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + assertEquals("view", modelAndView.getViewName()); + } + + @Test + public void viewNameAndStatus() throws Exception { + this.controller.setViewName("view"); + this.controller.setStatusCode(HttpStatus.NOT_FOUND); + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + assertEquals("view", modelAndView.getViewName()); + assertEquals(404, this.response.getStatus()); + } + + @Test + public void viewNameAndStatus204() throws Exception { + this.controller.setStatusCode(HttpStatus.NO_CONTENT); + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + assertNull(modelAndView); + assertEquals(204, this.response.getStatus()); + } + + @Test + public void redirectStatus() throws Exception { + this.controller.setStatusCode(HttpStatus.PERMANENT_REDIRECT); + this.controller.setViewName("/foo"); + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + + assertEquals("redirect:/foo", modelAndView.getViewName()); + assertEquals("3xx status should be left to RedirectView to set", 200, this.response.getStatus()); + assertEquals(HttpStatus.PERMANENT_REDIRECT, this.request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE)); + } + + @Test + public void redirectStatusWithRedirectPrefix() throws Exception { + this.controller.setStatusCode(HttpStatus.PERMANENT_REDIRECT); + this.controller.setViewName("redirect:/foo"); + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + + assertEquals("redirect:/foo", modelAndView.getViewName()); + assertEquals("3xx status should be left to RedirectView to set", 200, this.response.getStatus()); + assertEquals(HttpStatus.PERMANENT_REDIRECT, this.request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE)); + } + + @Test + public void redirectView() throws Exception { + RedirectView view = new RedirectView("/foo"); + this.controller.setView(view); + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + assertSame(view, modelAndView.getView()); + } + + @Test + public void statusOnly() throws Exception { + this.controller.setStatusCode(HttpStatus.NOT_FOUND); + this.controller.setStatusOnly(true); + ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response); + assertNull(modelAndView); + assertEquals(404, this.response.getStatus()); + } + +}