SPR-8447 Provide sufficient contextwherever possible when exceptions are raised in new @MVC classes.
This commit is contained in:
@@ -93,10 +93,6 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
ModelAndViewContainer mavContainer,
|
||||
Object...providedArgs) throws Exception {
|
||||
|
||||
if (!returnValueHandlers.supportsReturnType(getReturnType())) {
|
||||
throw new IllegalStateException("No suitable HandlerMethodReturnValueHandler for method " + toString());
|
||||
}
|
||||
|
||||
Object returnValue = invokeForRequest(request, mavContainer, providedArgs);
|
||||
|
||||
setResponseStatus((ServletWebRequest) request);
|
||||
@@ -110,9 +106,25 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
|
||||
mavContainer.setResolveView(true);
|
||||
|
||||
returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
|
||||
try {
|
||||
returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
|
||||
} catch (Exception ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private String getReturnValueHandlingErrorMessage(String message, Object returnValue) {
|
||||
StringBuilder sb = new StringBuilder(message);
|
||||
if (returnValue != null) {
|
||||
sb.append(" [type=" + returnValue.getClass().getName() + "] ");
|
||||
}
|
||||
sb.append("[value=" + returnValue + "]");
|
||||
return getDetailedErrorMessage(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response status according to the {@link ResponseStatus} annotation.
|
||||
*/
|
||||
|
||||
@@ -101,8 +101,10 @@ public class DefaultMethodReturnValueHandler implements HandlerMethodReturnValue
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// should not happen
|
||||
throw new UnsupportedOperationException();
|
||||
// should not happen..
|
||||
Method method = returnType.getMethod();
|
||||
String returnTypeName = returnType.getParameterType().getName();
|
||||
throw new UnsupportedOperationException("Unknown return type: " + returnTypeName + " in method: " + method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation.support;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
@@ -74,9 +75,9 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
return new HttpEntity<Object>(body, inputMessage.getHeaders());
|
||||
}
|
||||
|
||||
private Class<?> getHttpEntityType(MethodParameter methodParam) {
|
||||
Assert.isAssignable(HttpEntity.class, methodParam.getParameterType());
|
||||
ParameterizedType type = (ParameterizedType) methodParam.getGenericParameterType();
|
||||
private Class<?> getHttpEntityType(MethodParameter parameter) {
|
||||
Assert.isAssignable(HttpEntity.class, parameter.getParameterType());
|
||||
ParameterizedType type = (ParameterizedType) parameter.getGenericParameterType();
|
||||
if (type.getActualTypeArguments().length == 1) {
|
||||
Type typeArgument = type.getActualTypeArguments()[0];
|
||||
if (typeArgument instanceof Class) {
|
||||
@@ -91,8 +92,8 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"HttpEntity parameter (" + methodParam.getParameterName() + ") is not parameterized");
|
||||
throw new IllegalArgumentException("HttpEntity parameter (" + parameter.getParameterName() + ") "
|
||||
+ "in method " + parameter.getMethod() + "is not parameterized");
|
||||
}
|
||||
|
||||
public void handleReturnValue(Object returnValue,
|
||||
|
||||
@@ -65,24 +65,24 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
||||
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
NativeWebRequest request,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
|
||||
MultipartHttpServletRequest multipartServletRequest =
|
||||
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
|
||||
if (multipartServletRequest == null) {
|
||||
if (multipartRequest == null) {
|
||||
throw new IllegalStateException(
|
||||
"Current request is not of type " + MultipartRequest.class.getName());
|
||||
"Current request is not of type [" + MultipartRequest.class.getName() + "]: " + request);
|
||||
}
|
||||
|
||||
String partName = getPartName(parameter);
|
||||
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(multipartServletRequest, partName);
|
||||
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(multipartRequest, partName);
|
||||
|
||||
Object arg = readWithMessageConverters(inputMessage, parameter, parameter.getParameterType());
|
||||
|
||||
if (isValidationApplicable(arg, parameter)) {
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, partName);
|
||||
WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
|
||||
binder.validate();
|
||||
Errors errors = binder.getBindingResult();
|
||||
if (errors.hasErrors()) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation.support;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.Principal;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -101,8 +102,9 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume
|
||||
return request.getReader();
|
||||
}
|
||||
else {
|
||||
// should not happen
|
||||
throw new UnsupportedOperationException();
|
||||
// should never happen..
|
||||
Method method = parameter.getMethod();
|
||||
throw new UnsupportedOperationException("Unknown parameter type: " + paramType + " in method: " + method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation.support;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -66,25 +67,26 @@ public class ServletResponseMethodArgumentResolver implements HandlerMethodArgum
|
||||
mavContainer.setResolveView(false);
|
||||
|
||||
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
|
||||
if (ServletResponse.class.isAssignableFrom(parameterType)) {
|
||||
Object nativeResponse = webRequest.getNativeResponse(parameterType);
|
||||
if (ServletResponse.class.isAssignableFrom(paramType)) {
|
||||
Object nativeResponse = webRequest.getNativeResponse(paramType);
|
||||
if (nativeResponse == null) {
|
||||
throw new IllegalStateException(
|
||||
"Current response is not of type [" + parameterType.getName() + "]: " + response);
|
||||
"Current response is not of type [" + paramType.getName() + "]: " + response);
|
||||
}
|
||||
return nativeResponse;
|
||||
}
|
||||
else if (OutputStream.class.isAssignableFrom(parameterType)) {
|
||||
else if (OutputStream.class.isAssignableFrom(paramType)) {
|
||||
return response.getOutputStream();
|
||||
}
|
||||
else if (Writer.class.isAssignableFrom(parameterType)) {
|
||||
else if (Writer.class.isAssignableFrom(paramType)) {
|
||||
return response.getWriter();
|
||||
}
|
||||
else {
|
||||
// should not happen
|
||||
throw new UnsupportedOperationException();
|
||||
Method method = parameter.getMethod();
|
||||
throw new UnsupportedOperationException("Unknown parameter type: " + paramType + " in method: " + method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@@ -62,7 +64,9 @@ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHan
|
||||
}
|
||||
else {
|
||||
// should not happen
|
||||
throw new UnsupportedOperationException();
|
||||
Method method = returnType.getMethod();
|
||||
String returnTypeName = returnType.getParameterType().getName();
|
||||
throw new UnsupportedOperationException("Unknown return type: " + returnTypeName + " in method: " + method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
|
||||
|
||||
/**
|
||||
* Handle the case where the object created from the body of a request has failed validation.
|
||||
* The default implementation sends an HTTP 400 error along with a message containing the errors.
|
||||
* The default implementation sends an HTTP 400 error.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param handler the executed handler
|
||||
@@ -353,13 +353,13 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
|
||||
*/
|
||||
protected ModelAndView handleRequestBodyNotValidException(RequestBodyNotValidException ex,
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the case where the object created from the part of a multipart request has failed validation.
|
||||
* The default implementation sends an HTTP 400 error along with a message containing the errors.
|
||||
* The default implementation sends an HTTP 400 error.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param handler the executed handler
|
||||
@@ -368,7 +368,7 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
|
||||
*/
|
||||
protected ModelAndView handleRequestPartNotValidException(RequestPartNotValidException ex,
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
@@ -45,8 +47,6 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletResp
|
||||
*/
|
||||
public class ServletInvocableHandlerMethodTests {
|
||||
|
||||
private final Object handler = new Handler();
|
||||
|
||||
private HandlerMethodArgumentResolverComposite argumentResolvers;
|
||||
|
||||
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
|
||||
@@ -67,11 +67,11 @@ public class ServletInvocableHandlerMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setResponseStatus() throws Exception {
|
||||
returnValueHandlers.addHandler(new ExceptionThrowingReturnValueHandler());
|
||||
handlerMethod("responseStatus").invokeAndHandle(webRequest, mavContainer);
|
||||
public void nullReturnValueResponseStatus() throws Exception {
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatus");
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertFalse("Null return value with an @ResponseStatus should result in 'no view resolution'",
|
||||
assertFalse("Null return value + @ResponseStatus should result in 'no view resolution'",
|
||||
mavContainer.isResolveView());
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
|
||||
@@ -79,53 +79,59 @@ public class ServletInvocableHandlerMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkNoViewResolutionWithHttpServletResponse() throws Exception {
|
||||
public void nullReturnValueHttpServletResponseArg() throws Exception {
|
||||
argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
|
||||
returnValueHandlers.addHandler(new ExceptionThrowingReturnValueHandler());
|
||||
handlerMethod("httpServletResponse", HttpServletResponse.class).invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertFalse("Null return value with an HttpServletResponse argument should result in 'no view resolution'",
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("httpServletResponse", HttpServletResponse.class);
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertFalse("Null return value + HttpServletResponse arg should result in 'no view resolution'",
|
||||
mavContainer.isResolveView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkNoViewResolutionWithRequestNotModified() throws Exception {
|
||||
returnValueHandlers.addHandler(new ExceptionThrowingReturnValueHandler());
|
||||
|
||||
public void nullReturnValueRequestNotModified() throws Exception {
|
||||
webRequest.getNativeRequest(MockHttpServletRequest.class).addHeader("If-Modified-Since", 10 * 1000 * 1000);
|
||||
int lastModifiedTimestamp = 1000 * 1000;
|
||||
webRequest.checkNotModified(lastModifiedTimestamp);
|
||||
|
||||
handlerMethod("notModified").invokeAndHandle(webRequest, mavContainer);
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("notModified");
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertFalse("Null return value with a 'not modified' request should result in 'no view resolution'",
|
||||
assertFalse("Null return value + 'not modified' request should result in 'no view resolution'",
|
||||
mavContainer.isResolveView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exceptionWhileHandlingReturnValue() throws Exception {
|
||||
returnValueHandlers.addHandler(new ExceptionRaisingReturnValueHandler());
|
||||
|
||||
private ServletInvocableHandlerMethod handlerMethod(String methodName, Class<?>...paramTypes)
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("handle");
|
||||
try {
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
fail("Expected exception");
|
||||
} catch (HttpMessageNotWritableException ex) {
|
||||
// Expected..
|
||||
// Allow HandlerMethodArgumentResolver exceptions to propagate..
|
||||
}
|
||||
}
|
||||
|
||||
private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class<?>... argTypes)
|
||||
throws NoSuchMethodException {
|
||||
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
|
||||
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(handler, method);
|
||||
Method method = Handler.class.getDeclaredMethod(methodName, argTypes);
|
||||
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Handler(), method);
|
||||
handlerMethod.setHandlerMethodArgumentResolvers(argumentResolvers);
|
||||
handlerMethod.setHandlerMethodReturnValueHandlers(returnValueHandlers);
|
||||
return handlerMethod;
|
||||
}
|
||||
|
||||
private static class ExceptionThrowingReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType,
|
||||
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
|
||||
throw new IllegalStateException("Should never be invoked");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class Handler {
|
||||
|
||||
|
||||
public String handle() {
|
||||
return "view";
|
||||
}
|
||||
|
||||
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
|
||||
public void responseStatus() {
|
||||
}
|
||||
@@ -135,6 +141,19 @@ public class ServletInvocableHandlerMethodTests {
|
||||
|
||||
public void notModified() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType,
|
||||
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
|
||||
throw new HttpMessageNotWritableException("oops, can't write");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -144,8 +144,6 @@ public class DefaultHandlerExceptionResolverTests {
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertTrue("No Empty ModelAndView returned", mav.isEmpty());
|
||||
assertEquals("Invalid status code", 400, response.getStatus());
|
||||
assertTrue(response.getErrorMessage().startsWith("Request body content validation failed"));
|
||||
assertTrue(response.getErrorMessage().contains("Field error in object 'testBean' on field 'name'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -157,7 +155,5 @@ public class DefaultHandlerExceptionResolverTests {
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertTrue("No Empty ModelAndView returned", mav.isEmpty());
|
||||
assertEquals("Invalid status code", 400, response.getStatus());
|
||||
assertTrue(response.getErrorMessage().startsWith("Validation of the content of request part"));
|
||||
assertTrue(response.getErrorMessage().contains("Field error in object 'testBean' on field 'name'"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user