Skip to content

Commit 0e2c5ee

Browse files
committed
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.
1 parent dfcc1d7 commit 0e2c5ee

File tree

2 files changed

+239
-47
lines changed

2 files changed

+239
-47
lines changed
Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,82 +19,148 @@
1919
import javax.servlet.http.HttpServletRequest;
2020
import javax.servlet.http.HttpServletResponse;
2121

22+
import org.springframework.http.HttpStatus;
2223
import org.springframework.web.servlet.ModelAndView;
24+
import org.springframework.web.servlet.View;
2325
import org.springframework.web.servlet.support.RequestContextUtils;
2426

2527
/**
26-
* <p>Trivial controller that always returns a named view. The view
27-
* can be configured using an exposed configuration property. This
28-
* controller offers an alternative to sending a request straight to a view
29-
* such as a JSP. The advantage here is that the client is not exposed to
30-
* the concrete view technology but rather just to the controller URL;
31-
* the concrete view will be determined by the ViewResolver.
32-
*
33-
* <p>An alternative to the ParameterizableViewController is a
34-
* {@link org.springframework.web.servlet.mvc.multiaction.MultiActionController MultiActionController},
35-
* which can define a variety of handler methods that just return a plain
36-
* ModelAndView instance for a given view name.
37-
*
38-
* <p><b><a name="workflow">Workflow
39-
* (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
40-
* <ol>
41-
* <li>Request is received by the controller</li>
42-
* <li>call to {@link #handleRequestInternal handleRequestInternal} which
43-
* just returns the view, named by the configuration property
44-
* {@code viewName}. Nothing more, nothing less</li>
45-
* </ol>
46-
* </p>
47-
*
48-
* <p><b><a name="config">Exposed configuration properties</a>
49-
* (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
50-
* <table border="1">
51-
* <tr>
52-
* <td><b>name</b></td>
53-
* <td><b>default</b></td>
54-
* <td><b>description</b></td>
55-
* </tr>
56-
* <tr>
57-
* <td>viewName</td>
58-
* <td><i>null</i></td>
59-
* <td>the name of the view the viewResolver will use to forward to
60-
* (if this property is not set, a null view name will be returned
61-
* directing the caller to calculate the view name from the current request)</td>
62-
* </tr>
63-
* </table>
64-
* </p>
28+
* Trivial controller that always returns a pre-configured view and optionally
29+
* sets the response status code. The view and status can be configured using
30+
* the provided configuration properties.
6531
*
6632
* @author Rod Johnson
6733
* @author Juergen Hoeller
6834
* @author Keith Donald
35+
* @author Rossen Stoyanchev
6936
*/
7037
public class ParameterizableViewController extends AbstractController {
7138

72-
private String viewName;
39+
private Object view;
40+
41+
private HttpStatus statusCode;
42+
43+
private boolean statusOnly;
7344

7445

7546
/**
76-
* Set the name of the view to delegate to.
47+
* Set a view name for the ModelAndView to return, to be resolved by the
48+
* DispatcherServlet via a ViewResolver. Will override any pre-existing
49+
* view name or View.
7750
*/
7851
public void setViewName(String viewName) {
79-
this.viewName = viewName;
52+
this.view = viewName;
8053
}
8154

8255
/**
83-
* Return the name of the view to delegate to.
56+
* Return the name of the view to delegate to, or {@code null} if using a
57+
* View instance.
8458
*/
8559
public String getViewName() {
86-
return this.viewName;
60+
return (this.view instanceof String ? (String) this.view : null);
61+
}
62+
63+
/**
64+
* Set a View object for the ModelAndView to return.
65+
* Will override any pre-existing view name or View.
66+
* @since 4.1
67+
*/
68+
public void setView(View view) {
69+
this.view = view;
70+
}
71+
72+
/**
73+
* Return the View object, or {@code null} if we are using a view name
74+
* to be resolved by the DispatcherServlet via a ViewResolver.
75+
* @since 4.1
76+
*/
77+
public View getView() {
78+
return (this.view instanceof View ? (View) this.view : null);
79+
}
80+
81+
/**
82+
* Configure the HTTP status code that this controller should set on the
83+
* response.
84+
*
85+
* <p>When a "redirect:" prefixed view name is configured, there is no need
86+
* to set this property since RedirectView will do that. However this property
87+
* may still be used to override the 3xx status code of {@code RedirectView}.
88+
* For full control over redirecting provide a {@code RedirectView} instance.
89+
*
90+
* <p>If the status code is 204 and no view is configured, the request is
91+
* fully handled within the controller.
92+
*
93+
* @since 4.1
94+
*/
95+
public void setStatusCode(HttpStatus statusCode) {
96+
this.statusCode = statusCode;
97+
}
98+
99+
/**
100+
* Return the configured HTTP status code or {@code null}.
101+
* @since 4.1
102+
*/
103+
public HttpStatus getStatusCode() {
104+
return this.statusCode;
105+
}
106+
107+
108+
/**
109+
* The property can be used to indicate the request is considered fully
110+
* handled within the controller and that no view should be used for rendering.
111+
* Useful in combination with {@link #setStatusCode}.
112+
* <p>By default this is set to {@code false}.
113+
* @since 4.1
114+
*/
115+
public void setStatusOnly(boolean statusOnly) {
116+
this.statusOnly = statusOnly;
117+
}
118+
119+
/**
120+
* Whether the request is fully handled within the controller.
121+
*/
122+
public boolean isStatusOnly() {
123+
return this.statusOnly;
87124
}
88125

89126
/**
90127
* Return a ModelAndView object with the specified view name.
91-
* The content of {@link RequestContextUtils#getInputFlashMap} is also added to the model.
128+
*
129+
* <p>The content of the {@link RequestContextUtils#getInputFlashMap
130+
* "input" FlashMap} is also added to the model.
131+
*
92132
* @see #getViewName()
93133
*/
94134
@Override
95135
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
96136
throws Exception {
97-
return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request));
137+
138+
String viewName = getViewName();
139+
140+
if (getStatusCode() != null) {
141+
if (getStatusCode().is3xxRedirection()) {
142+
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, getStatusCode());
143+
viewName = (viewName != null && !viewName.startsWith("redirect:") ? "redirect:" + viewName : viewName);
144+
}
145+
else {
146+
response.setStatus(getStatusCode().value());
147+
if (isStatusOnly() || (getStatusCode().equals(HttpStatus.NO_CONTENT) && getViewName() == null)) {
148+
return null;
149+
}
150+
}
151+
}
152+
153+
ModelAndView modelAndView = new ModelAndView();
154+
modelAndView.addAllObjects(RequestContextUtils.getInputFlashMap(request));
155+
156+
if (getViewName() != null) {
157+
modelAndView.setViewName(viewName);
158+
}
159+
else {
160+
modelAndView.setView(getView());
161+
}
162+
163+
return (isStatusOnly() ? null : modelAndView);
98164
}
99165

100166
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.mvc.support;
18+
19+
import org.junit.Before;
20+
import org.junit.Test;
21+
import org.springframework.http.HttpStatus;
22+
import org.springframework.mock.web.test.MockHttpServletRequest;
23+
import org.springframework.mock.web.test.MockHttpServletResponse;
24+
import org.springframework.web.servlet.ModelAndView;
25+
import org.springframework.web.servlet.View;
26+
import org.springframework.web.servlet.mvc.ParameterizableViewController;
27+
import org.springframework.web.servlet.view.RedirectView;
28+
29+
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertNull;
31+
import static org.junit.Assert.assertSame;
32+
33+
/**
34+
* Unit tests for
35+
* {@link org.springframework.web.servlet.mvc.ParameterizableViewController}.
36+
*
37+
* @author Rossen Stoyanchev
38+
* @since 4.1
39+
*/
40+
public class ParameterizableViewControllerTests {
41+
42+
private ParameterizableViewController controller;
43+
44+
private MockHttpServletRequest request;
45+
46+
private MockHttpServletResponse response;
47+
48+
49+
@Before
50+
public void setUp() throws Exception {
51+
this.controller = new ParameterizableViewController();
52+
this.request = new MockHttpServletRequest("GET", "/");
53+
this.response = new MockHttpServletResponse();
54+
}
55+
56+
57+
@Test
58+
public void defaultViewName() throws Exception {
59+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
60+
assertNull(modelAndView.getViewName());
61+
}
62+
63+
@Test
64+
public void viewName() throws Exception {
65+
this.controller.setViewName("view");
66+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
67+
assertEquals("view", modelAndView.getViewName());
68+
}
69+
70+
@Test
71+
public void viewNameAndStatus() throws Exception {
72+
this.controller.setViewName("view");
73+
this.controller.setStatusCode(HttpStatus.NOT_FOUND);
74+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
75+
assertEquals("view", modelAndView.getViewName());
76+
assertEquals(404, this.response.getStatus());
77+
}
78+
79+
@Test
80+
public void viewNameAndStatus204() throws Exception {
81+
this.controller.setStatusCode(HttpStatus.NO_CONTENT);
82+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
83+
assertNull(modelAndView);
84+
assertEquals(204, this.response.getStatus());
85+
}
86+
87+
@Test
88+
public void redirectStatus() throws Exception {
89+
this.controller.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
90+
this.controller.setViewName("/foo");
91+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
92+
93+
assertEquals("redirect:/foo", modelAndView.getViewName());
94+
assertEquals("3xx status should be left to RedirectView to set", 200, this.response.getStatus());
95+
assertEquals(HttpStatus.PERMANENT_REDIRECT, this.request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE));
96+
}
97+
98+
@Test
99+
public void redirectStatusWithRedirectPrefix() throws Exception {
100+
this.controller.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
101+
this.controller.setViewName("redirect:/foo");
102+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
103+
104+
assertEquals("redirect:/foo", modelAndView.getViewName());
105+
assertEquals("3xx status should be left to RedirectView to set", 200, this.response.getStatus());
106+
assertEquals(HttpStatus.PERMANENT_REDIRECT, this.request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE));
107+
}
108+
109+
@Test
110+
public void redirectView() throws Exception {
111+
RedirectView view = new RedirectView("/foo");
112+
this.controller.setView(view);
113+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
114+
assertSame(view, modelAndView.getView());
115+
}
116+
117+
@Test
118+
public void statusOnly() throws Exception {
119+
this.controller.setStatusCode(HttpStatus.NOT_FOUND);
120+
this.controller.setStatusOnly(true);
121+
ModelAndView modelAndView = this.controller.handleRequest(this.request, this.response);
122+
assertNull(modelAndView);
123+
assertEquals(404, this.response.getStatus());
124+
}
125+
126+
}

0 commit comments

Comments
 (0)