Browse Source

Enhance ParameterizableViewController

This change two new capabilities to ParameterizableViewController:

 - configure a View instance (in addition to view name)
 - configure response status code

The status code may be useful to send a 404 while also writing to the
body using a view.

The status code may also be used to override the redirect status code
of RedirectView. Even today it's possible to configure a "redirect:"
prefixed view name but the status code could not be selected. When a
3xx status is set, the code is passed on to the RedirectView while the
view name is automatically prefixed with "redirect:" (if not already).

For full control over RedirectView it is now also possible to
parameterize the controller with a View instance.

As one more possible resulting variation, given status 204 and no view
the request is considered handled (controller returns null).

This change is preparation for SPR-11543.
pull/552/merge
Rossen Stoyanchev 11 years ago
parent
commit
0e2c5ee96c
  1. 160
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java
  2. 126
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/ParameterizableViewControllerTests.java

160
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java

@ -1,5 +1,5 @@ @@ -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; @@ -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;
/**
* <p>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.
*
* <p>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.
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
* <ol>
* <li>Request is received by the controller</li>
* <li>call to {@link #handleRequestInternal handleRequestInternal} which
* just returns the view, named by the configuration property
* {@code viewName}. Nothing more, nothing less</li>
* </ol>
* </p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>viewName</td>
* <td><i>null</i></td>
* <td>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)</td>
* </tr>
* </table>
* </p>
* Trivial controller that always returns a pre-configured view and optionally
* sets the response status code. The view and status can be configured using
* the provided configuration properties.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Keith Donald
* @author Rossen Stoyanchev
*/
public class ParameterizableViewController extends AbstractController {
private String viewName;
private Object view;
private HttpStatus statusCode;
private boolean statusOnly;
/**
* Set the name of the view to delegate to.
* Set a view name for the ModelAndView to return, to be resolved by the
* DispatcherServlet via a ViewResolver. Will override any pre-existing
* view name or View.
*/
public void setViewName(String viewName) {
this.viewName = viewName;
this.view = viewName;
}
/**
* Return the name of the view to delegate to.
* Return the name of the view to delegate to, or {@code null} if using a
* View instance.
*/
public String getViewName() {
return this.viewName;
return (this.view instanceof String ? (String) this.view : null);
}
/**
* Set a View object for the ModelAndView to return.
* Will override any pre-existing view name or View.
* @since 4.1
*/
public void setView(View view) {
this.view = view;
}
/**
* Return the View object, or {@code null} if we are using a view name
* to be resolved by the DispatcherServlet via a ViewResolver.
* @since 4.1
*/
public View getView() {
return (this.view instanceof View ? (View) this.view : null);
}
/**
* Configure the HTTP status code that this controller should set on the
* response.
*
* <p>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.
*
* <p>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}.
* <p>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.
*
* <p>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);
}
}

126
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/ParameterizableViewControllerTests.java

@ -0,0 +1,126 @@ @@ -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());
}
}
Loading…
Cancel
Save