Skip to content

Commit 12e43ff

Browse files
committed
SPR-5953 - Allow SimpleMappingExceptionResolver to Resolve HTTP Status Codes
1 parent 2d4ae59 commit 12e43ff

File tree

2 files changed

+110
-39
lines changed

2 files changed

+110
-39
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818

1919
import java.util.Enumeration;
2020
import java.util.Properties;
21+
import java.util.Map;
22+
import java.util.Iterator;
23+
import java.util.HashMap;
2124
import javax.servlet.http.HttpServletRequest;
2225
import javax.servlet.http.HttpServletResponse;
2326

2427
import org.springframework.web.servlet.ModelAndView;
2528
import org.springframework.web.util.WebUtils;
29+
import org.springframework.util.CollectionUtils;
2630

2731
/**
2832
* {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation that allows for mapping exception
@@ -47,6 +51,8 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso
4751

4852
private Integer defaultStatusCode;
4953

54+
private Map<String, Integer> statusCodes = new HashMap<String, Integer>();
55+
5056
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
5157

5258
/**
@@ -77,14 +83,37 @@ public void setDefaultErrorView(String defaultErrorView) {
7783
}
7884

7985
/**
80-
* Set the default HTTP status code that this exception resolver will apply if it resolves an error view. <p>Note that
81-
* this error code will only get applied in case of a top-level request. It will not be set for an include request,
82-
* since the HTTP status cannot be modified from within an include. <p>If not specified, no status code will be
83-
* applied, either leaving this to the controller or view, or keeping the servlet engine's default of 200 (OK).
86+
* Set the HTTP status code that this exception resolver will apply for a given resolved error view. Keys are
87+
* view names; values are status codes.
88+
*
89+
* <p>Note that this error code will only get applied in case of a top-level request. It will not be set for an include
90+
* request, since the HTTP status cannot be modified from within an include.
91+
*
92+
* <p>If not specified, the default status code will be applied.
93+
*
94+
* @see #setDefaultStatusCode(int)
95+
*/
96+
public void setStatusCodes(Properties statusCodes) {
97+
for (Enumeration enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) {
98+
String viewName = (String) enumeration.nextElement();
99+
Integer statusCode = new Integer(statusCodes.getProperty(viewName));
100+
this.statusCodes.put(viewName, statusCode);
101+
}
102+
}
103+
104+
/**
105+
* Set the default HTTP status code that this exception resolver will apply if it resolves an error view and if there
106+
* is no status code mapping defined.
107+
*
108+
* <p>Note that this error code will only get applied in case of a top-level request. It will not be set for an
109+
* include request, since the HTTP status cannot be modified from within an include.
110+
*
111+
* <p>If not specified, no status code will be applied, either leaving this to the controller or view, or keeping
112+
* the servlet engine's default of 200 (OK).
84113
*
85-
* @param defaultStatusCode HTTP status code value, for example 500 (SC_INTERNAL_SERVER_ERROR) or 404 (SC_NOT_FOUND)
86-
* @see javax.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR
87-
* @see javax.servlet.http.HttpServletResponse#SC_NOT_FOUND
114+
* @param defaultStatusCode HTTP status code value, for example 500
115+
* ({@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR}) or 404 ({@link HttpServletResponse#SC_NOT_FOUND})
116+
* @see #setStatusCodes(Properties)
88117
*/
89118
public void setDefaultStatusCode(int defaultStatusCode) {
90119
this.defaultStatusCode = defaultStatusCode;
@@ -210,9 +239,13 @@ private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
210239
}
211240

212241
/**
213-
* Determine the HTTP status code to apply for the given error view. <p>The default implementation always returns the
214-
* specified {@link #setDefaultStatusCode "defaultStatusCode"}, as a common status code for all error views. Override
215-
* this in a custom subclass to determine a specific status code for the given view.
242+
* Determine the HTTP status code to apply for the given error view.
243+
*
244+
* <p>The default implementation returns the status code for the given view name (specified through the
245+
* {@link #setStatusCodes(Properties) statusCodes} property), or falls back to the
246+
* {@link #setDefaultStatusCode defaultStatusCode} if there is no match.
247+
*
248+
* <p>Override this in a custom subclass to customize this behavior.
216249
*
217250
* @param request current HTTP request
218251
* @param viewName the name of the error view
@@ -222,6 +255,9 @@ private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
222255
* @see #applyStatusCodeIfPossible
223256
*/
224257
protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
258+
if (this.statusCodes.containsKey(viewName)) {
259+
return this.statusCodes.get(viewName);
260+
}
225261
return this.defaultStatusCode;
226262
}
227263

@@ -234,7 +270,7 @@ protected Integer determineStatusCode(HttpServletRequest request, String viewNam
234270
* @param statusCode the status code to apply
235271
* @see #determineStatusCode
236272
* @see #setDefaultStatusCode
237-
* @see javax.servlet.http.HttpServletResponse#setStatus
273+
* @see HttpServletResponse#setStatus
238274
*/
239275
protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
240276
if (!WebUtils.isIncludeRequest(request)) {

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolverTests.java

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818

1919
import java.util.Collections;
2020
import java.util.Properties;
21-
2221
import javax.servlet.http.HttpServletResponse;
2322

24-
import junit.framework.TestCase;
23+
import static org.junit.Assert.*;
24+
import org.junit.Before;
25+
import org.junit.Test;
2526

2627
import org.springframework.mock.web.MockHttpServletRequest;
2728
import org.springframework.mock.web.MockHttpServletResponse;
@@ -31,8 +32,9 @@
3132
/**
3233
* @author Seth Ladd
3334
* @author Juergen Hoeller
35+
* @author Arjen Poutsma
3436
*/
35-
public class SimpleMappingExceptionResolverTests extends TestCase {
37+
public class SimpleMappingExceptionResolverTests {
3638

3739
private SimpleMappingExceptionResolver exceptionResolver;
3840
private MockHttpServletRequest request;
@@ -41,7 +43,8 @@ public class SimpleMappingExceptionResolverTests extends TestCase {
4143
private Object handler2;
4244
private Exception genericException;
4345

44-
protected void setUp() throws Exception {
46+
@Before
47+
public void setUp() throws Exception {
4548
exceptionResolver = new SimpleMappingExceptionResolver();
4649
handler1 = new String();
4750
handler2 = new Object();
@@ -51,69 +54,90 @@ protected void setUp() throws Exception {
5154
genericException = new Exception();
5255
}
5356

54-
public void testSetOrder() {
57+
@Test
58+
public void setOrder() {
5559
exceptionResolver.setOrder(2);
5660
assertEquals(2, exceptionResolver.getOrder());
5761
}
5862

59-
public void testDefaultErrorView() {
63+
@Test
64+
public void defaultErrorView() {
6065
exceptionResolver.setDefaultErrorView("default-view");
6166
ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, genericException);
6267
assertEquals("default-view", mav.getViewName());
6368
assertEquals(genericException, mav.getModel().get(SimpleMappingExceptionResolver.DEFAULT_EXCEPTION_ATTRIBUTE));
6469
}
6570

66-
public void testDefaultErrorViewDifferentHandler() {
71+
@Test
72+
public void defaultErrorViewDifferentHandler() {
6773
exceptionResolver.setDefaultErrorView("default-view");
6874
exceptionResolver.setMappedHandlers(Collections.singleton(handler1));
6975
ModelAndView mav = exceptionResolver.resolveException(request, response, handler2, genericException);
7076
assertNull(mav);
7177
}
7278

73-
public void testDefaultErrorViewDifferentHandlerClass() {
79+
@Test
80+
public void defaultErrorViewDifferentHandlerClass() {
7481
exceptionResolver.setDefaultErrorView("default-view");
7582
exceptionResolver.setMappedHandlerClasses(new Class[] {String.class});
7683
ModelAndView mav = exceptionResolver.resolveException(request, response, handler2, genericException);
7784
assertNull(mav);
7885
}
7986

80-
public void testNullExceptionAttribute() {
87+
@Test
88+
public void nullExceptionAttribute() {
8189
exceptionResolver.setDefaultErrorView("default-view");
8290
exceptionResolver.setExceptionAttribute(null);
8391
ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, genericException);
8492
assertEquals("default-view", mav.getViewName());
8593
assertNull(mav.getModel().get(SimpleMappingExceptionResolver.DEFAULT_EXCEPTION_ATTRIBUTE));
8694
}
8795

88-
public void testNullExceptionMappings() {
96+
@Test
97+
public void nullExceptionMappings() {
8998
exceptionResolver.setExceptionMappings(null);
9099
exceptionResolver.setDefaultErrorView("default-view");
91100
ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, genericException);
92101
assertEquals("default-view", mav.getViewName());
93102
}
94103

95-
public void testNoDefaultStatusCode() {
104+
@Test
105+
public void noDefaultStatusCode() {
96106
exceptionResolver.setDefaultErrorView("default-view");
97-
ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, genericException);
107+
exceptionResolver.resolveException(request, response, handler1, genericException);
98108
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
99109
}
100110

101-
public void testSetDefaultStatusCode() {
111+
@Test
112+
public void setDefaultStatusCode() {
102113
exceptionResolver.setDefaultErrorView("default-view");
103114
exceptionResolver.setDefaultStatusCode(HttpServletResponse.SC_BAD_REQUEST);
104-
ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, genericException);
115+
exceptionResolver.resolveException(request, response, handler1, genericException);
105116
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
106117
}
107118

108-
public void testNoDefaultStatusCodeInInclude() {
119+
@Test
120+
public void noDefaultStatusCodeInInclude() {
109121
exceptionResolver.setDefaultErrorView("default-view");
110122
exceptionResolver.setDefaultStatusCode(HttpServletResponse.SC_BAD_REQUEST);
111123
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "some path");
112-
ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, genericException);
124+
exceptionResolver.resolveException(request, response, handler1, genericException);
113125
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
114126
}
115127

116-
public void testSimpleExceptionMapping() {
128+
@Test
129+
public void specificStatusCode() {
130+
exceptionResolver.setDefaultErrorView("default-view");
131+
exceptionResolver.setDefaultStatusCode(HttpServletResponse.SC_BAD_REQUEST);
132+
Properties statusCodes = new Properties();
133+
statusCodes.setProperty("default-view", "406");
134+
exceptionResolver.setStatusCodes(statusCodes);
135+
exceptionResolver.resolveException(request, response, handler1, genericException);
136+
assertEquals(HttpServletResponse.SC_NOT_ACCEPTABLE, response.getStatus());
137+
}
138+
139+
@Test
140+
public void simpleExceptionMapping() {
117141
Properties props = new Properties();
118142
props.setProperty("Exception", "error");
119143
exceptionResolver.setWarnLogCategory("HANDLER_EXCEPTION");
@@ -122,7 +146,8 @@ public void testSimpleExceptionMapping() {
122146
assertEquals("error", mav.getViewName());
123147
}
124148

125-
public void testExactExceptionMappingWithHandlerSpecified() {
149+
@Test
150+
public void exactExceptionMappingWithHandlerSpecified() {
126151
Properties props = new Properties();
127152
props.setProperty("java.lang.Exception", "error");
128153
exceptionResolver.setMappedHandlers(Collections.singleton(handler1));
@@ -131,7 +156,8 @@ public void testExactExceptionMappingWithHandlerSpecified() {
131156
assertEquals("error", mav.getViewName());
132157
}
133158

134-
public void testExactExceptionMappingWithHandlerClassSpecified() {
159+
@Test
160+
public void exactExceptionMappingWithHandlerClassSpecified() {
135161
Properties props = new Properties();
136162
props.setProperty("java.lang.Exception", "error");
137163
exceptionResolver.setMappedHandlerClasses(new Class[] {String.class});
@@ -140,7 +166,8 @@ public void testExactExceptionMappingWithHandlerClassSpecified() {
140166
assertEquals("error", mav.getViewName());
141167
}
142168

143-
public void testExactExceptionMappingWithHandlerInterfaceSpecified() {
169+
@Test
170+
public void exactExceptionMappingWithHandlerInterfaceSpecified() {
144171
Properties props = new Properties();
145172
props.setProperty("java.lang.Exception", "error");
146173
exceptionResolver.setMappedHandlerClasses(new Class[] {Comparable.class});
@@ -149,7 +176,8 @@ public void testExactExceptionMappingWithHandlerInterfaceSpecified() {
149176
assertEquals("error", mav.getViewName());
150177
}
151178

152-
public void testSimpleExceptionMappingWithHandlerSpecifiedButWrongHandler() {
179+
@Test
180+
public void simpleExceptionMappingWithHandlerSpecifiedButWrongHandler() {
153181
Properties props = new Properties();
154182
props.setProperty("Exception", "error");
155183
exceptionResolver.setMappedHandlers(Collections.singleton(handler1));
@@ -158,7 +186,8 @@ public void testSimpleExceptionMappingWithHandlerSpecifiedButWrongHandler() {
158186
assertNull(mav);
159187
}
160188

161-
public void testSimpleExceptionMappingWithHandlerClassSpecifiedButWrongHandler() {
189+
@Test
190+
public void simpleExceptionMappingWithHandlerClassSpecifiedButWrongHandler() {
162191
Properties props = new Properties();
163192
props.setProperty("Exception", "error");
164193
exceptionResolver.setMappedHandlerClasses(new Class[] {String.class});
@@ -167,7 +196,8 @@ public void testSimpleExceptionMappingWithHandlerClassSpecifiedButWrongHandler()
167196
assertNull(mav);
168197
}
169198

170-
public void testMissingExceptionInMapping() {
199+
@Test
200+
public void missingExceptionInMapping() {
171201
Properties props = new Properties();
172202
props.setProperty("SomeFooThrowable", "error");
173203
exceptionResolver.setWarnLogCategory("HANDLER_EXCEPTION");
@@ -176,7 +206,8 @@ public void testMissingExceptionInMapping() {
176206
assertNull(mav);
177207
}
178208

179-
public void testTwoMappings() {
209+
@Test
210+
public void twoMappings() {
180211
Properties props = new Properties();
181212
props.setProperty("java.lang.Exception", "error");
182213
props.setProperty("AnotherException", "another-error");
@@ -186,7 +217,8 @@ public void testTwoMappings() {
186217
assertEquals("error", mav.getViewName());
187218
}
188219

189-
public void testTwoMappingsOneShortOneLong() {
220+
@Test
221+
public void twoMappingsOneShortOneLong() {
190222
Properties props = new Properties();
191223
props.setProperty("Exception", "error");
192224
props.setProperty("AnotherException", "another-error");
@@ -196,7 +228,8 @@ public void testTwoMappingsOneShortOneLong() {
196228
assertEquals("error", mav.getViewName());
197229
}
198230

199-
public void testTwoMappingsOneShortOneLongThrowOddException() {
231+
@Test
232+
public void twoMappingsOneShortOneLongThrowOddException() {
200233
Exception oddException = new SomeOddException();
201234
Properties props = new Properties();
202235
props.setProperty("Exception", "error");
@@ -207,7 +240,8 @@ public void testTwoMappingsOneShortOneLongThrowOddException() {
207240
assertEquals("error", mav.getViewName());
208241
}
209242

210-
public void testTwoMappingsThrowOddExceptionUseLongExceptionMapping() {
243+
@Test
244+
public void twoMappingsThrowOddExceptionUseLongExceptionMapping() {
211245
Exception oddException = new SomeOddException();
212246
Properties props = new Properties();
213247
props.setProperty("java.lang.Exception", "error");
@@ -218,7 +252,8 @@ public void testTwoMappingsThrowOddExceptionUseLongExceptionMapping() {
218252
assertEquals("another-error", mav.getViewName());
219253
}
220254

221-
public void testThreeMappings() {
255+
@Test
256+
public void threeMappings() {
222257
Exception oddException = new AnotherOddException();
223258
Properties props = new Properties();
224259
props.setProperty("java.lang.Exception", "error");

0 commit comments

Comments
 (0)