From 0dae1a6bd8eff9d19670ebc0019789759af4394f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 29 Jun 2011 15:36:18 +0000 Subject: [PATCH] SPR-8447 Provide sufficient contextwherever possible when exceptions are raised in new @MVC classes. --- .../ServletInvocableHandlerMethod.java | 22 +- .../DefaultMethodReturnValueHandler.java | 6 +- .../support/HttpEntityMethodProcessor.java | 11 +- .../RequestPartMethodArgumentResolver.java | 14 +- .../ServletRequestMethodArgumentResolver.java | 6 +- ...ServletResponseMethodArgumentResolver.java | 16 +- .../support/ViewMethodReturnValueHandler.java | 6 +- .../DefaultHandlerExceptionResolver.java | 8 +- .../ServletInvocableHandlerMethodTests.java | 81 ++++--- .../DefaultHandlerExceptionResolverTests.java | 4 - .../InitBinderDataBinderFactory.java | 2 +- .../web/method/annotation/ModelFactory.java | 3 +- .../AbstractWebArgumentResolverAdapter.java | 6 +- .../support/ErrorsMethodArgumentResolver.java | 5 +- ...ExpressionValueMethodArgumentResolver.java | 2 +- .../support/ModelMethodProcessor.java | 5 +- ...andlerMethodArgumentResolverComposite.java | 11 +- ...dlerMethodReturnValueHandlerComposite.java | 11 +- .../support/InvocableHandlerMethod.java | 112 +++++---- ...rMethodArgumentResolverCompositeTests.java | 2 +- ...ethodReturnValueHandlerCompositeTests.java | 2 +- .../support/InvocableHandlerMethodTests.java | 212 ++++++++++++++---- .../method/support/StubArgumentResolver.java | 7 +- 23 files changed, 372 insertions(+), 182 deletions(-) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java index b1db82f2a6..60d42f6d36 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java @@ -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. */ diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java index 4297751f74..c3e4ba0b1d 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java @@ -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); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java index 30340eb66e..9e51874ca0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java @@ -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(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, diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java index 259c1240c8..f3f85fcf7a 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java @@ -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()) { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolver.java index 38aa461122..03ac3dff47 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolver.java @@ -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); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java index e971415fc4..2f4ce25402 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java @@ -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); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java index eb563cb5a7..e634d08961 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java @@ -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); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java index 00b011bb2b..dc87a77bec 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java @@ -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(); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java index ff22e8bf78..dcd901c8d8 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java @@ -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()); + + ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("handle"); + try { + handlerMethod.invokeAndHandle(webRequest, mavContainer); + fail("Expected exception"); + } catch (HttpMessageNotWritableException ex) { + // Expected.. + // Allow HandlerMethodArgumentResolver exceptions to propagate.. + } + } - private ServletInvocableHandlerMethod handlerMethod(String methodName, Class...paramTypes) + 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 { + @SuppressWarnings("unused") + private static class Handler { - public boolean supportsReturnType(MethodParameter returnType) { - return true; + public String handle() { + return "view"; } - 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 { - @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"); + } } } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java index 8c6c3f2738..1db389a17b 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java @@ -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'")); } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java index 9a8276dd40..e32034033e 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java @@ -64,7 +64,7 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { } Object returnValue = binderMethod.invokeForRequest(request, null, binder); if (returnValue != null) { - throw new IllegalStateException("This @InitBinder method does not return void: " + binderMethod); + throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod); } } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java index e9ee775c81..49ee5128d4 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java @@ -141,7 +141,8 @@ public final class ModelFactory { if (sessionHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) { Object attrValue = sessionHandler.retrieveAttribute(request, name); if (attrValue == null){ - throw new HttpSessionRequiredException("Session attribute '" + name + "' not found in session"); + throw new HttpSessionRequiredException( + "Session attribute '" + name + "' not found in session: " + requestMethod); } mavContainer.addAttribute(name, attrValue); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java index 36a883332c..fc6879f299 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java @@ -99,9 +99,9 @@ public abstract class AbstractWebArgumentResolverAdapter implements HandlerMetho Object result = adaptee.resolveArgument(parameter, webRequest); if (result == WebArgumentResolver.UNRESOLVED || !ClassUtils.isAssignableValue(paramType, result)) { throw new IllegalStateException( - "Standard argument type [" + paramType.getName() + "] resolved to incompatible value of type [" + - (result != null ? result.getClass() : null) + - "]. Consider declaring the argument type in a less specific fashion."); + "Standard argument type [" + paramType.getName() + "] in method " + parameter.getMethod() + + "resolved to incompatible value of type [" + (result != null ? result.getClass() : null) + + "]. Consider declaring the argument type in a less specific fashion."); } return result; } diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java index ddddac2ef6..c66019a925 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java @@ -56,8 +56,9 @@ public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolv } } - throw new IllegalStateException("Errors/BindingResult argument declared " - + "without preceding model attribute. Check your handler method signature!"); + throw new IllegalStateException( + "An Errors/BindingResult argument must follow a model attribute argument. " + + "Check your handler method signature: " + parameter.getMethod()); } } \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java index 9e128a0b46..05586ca08f 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java @@ -66,7 +66,7 @@ public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMet @Override protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException { - throw new UnsupportedOperationException("Did not expect to handle a missing value: an @Value is never required"); + throw new UnsupportedOperationException("@Value is never required: " + parameter.getMethod()); } private static class ExpressionValueNamedValueInfo extends NamedValueInfo { diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java index e53d4592d1..ad4fa8cd51 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java @@ -16,6 +16,7 @@ package org.springframework.web.method.annotation.support; +import java.lang.reflect.Method; import java.util.Map; import org.springframework.core.MethodParameter; @@ -76,7 +77,9 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand } 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); } } } \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java index 4e847098b8..82b04f2cd9 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -61,14 +62,8 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); - if (resolver != null) { - return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); - } - else { - throw new IllegalStateException( - "No suitable HandlerMethodArgumentResolver found. " + - "supportsParameter(MethodParameter) should have been called previously."); - } + Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]"); + return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } /** diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java index 0208e17d2b..a6f8a57477 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; import org.springframework.web.context.request.NativeWebRequest; /** @@ -60,14 +61,8 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); - if (handler != null) { - handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); - } - else { - throw new IllegalStateException( - "No suitable HandlerMethodReturnValueHandler found. " + - "supportsReturnType(MethodParameter) should have been called previously"); - } + Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]"); + handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } /** diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index b502856154..f6620bea35 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java @@ -142,25 +142,52 @@ public class InvocableHandlerMethod extends HandlerMethod { Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; - parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); + parameter.initParameterNameDiscovery(parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } - if (this.argumentResolvers.supportsParameter(parameter)) { - args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); + + if (argumentResolvers.supportsParameter(parameter)) { + try { + args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); + continue; + } catch (Exception ex) { + if (logger.isTraceEnabled()) { + logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); + } + throw ex; + } } - else { - throw new IllegalStateException("Cannot resolve argument index=" + parameter.getParameterIndex() + "" - + ", name=" + parameter.getParameterName() + ", type=" + parameter.getParameterType() - + " in method " + toString()); + + if (args[i] == null) { + String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); + throw new IllegalStateException(msg); } } return args; } + private String getArgumentResolutionErrorMessage(String message, int index) { + MethodParameter param = getMethodParameters()[index]; + message += " [" + index + "] [type=" + param.getParameterType().getName() + "]"; + return getDetailedErrorMessage(message); + } + + /** + * Adds HandlerMethod details such as the controller type and method signature to the given error message. + * @param message error message to append the HandlerMethod details to + */ + protected String getDetailedErrorMessage(String message) { + StringBuilder sb = new StringBuilder(message).append("\n"); + sb.append("HandlerMethod details: \n"); + sb.append("Controller [").append(getBeanType().getName()).append("]\n"); + sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n"); + return sb.toString(); + } + /** * Attempt to resolve a method parameter from the list of provided argument values. */ @@ -177,55 +204,50 @@ public class InvocableHandlerMethod extends HandlerMethod { } /** - * Invoke this handler method with the given argument values. + * Invoke the handler method with the given argument values. */ private Object invoke(Object... args) throws Exception { ReflectionUtils.makeAccessible(this.getBridgedMethod()); try { return getBridgedMethod().invoke(getBean(), args); } - catch (IllegalArgumentException ex) { - handleIllegalArgumentException(ex, args); - throw ex; + catch (IllegalArgumentException e) { + String msg = getInvocationErrorMessage(e.getMessage(), args); + throw new IllegalArgumentException(msg, e); } - catch (InvocationTargetException ex) { - handleInvocationTargetException(ex); - throw new IllegalStateException( - "Unexpected exception thrown by method - " + ex.getTargetException().getClass().getName() + ": " + - ex.getTargetException().getMessage()); - } - } - - private void handleIllegalArgumentException(IllegalArgumentException ex, Object... args) { - StringBuilder builder = new StringBuilder(ex.getMessage()); - builder.append(" :: method=").append(getBridgedMethod().toGenericString()); - builder.append(" :: invoked with handler type=").append(getBeanType().getName()); - - if (args != null && args.length > 0) { - builder.append(" and argument types "); - for (int i = 0; i < args.length; i++) { - String argClass = (args[i] != null) ? args[i].getClass().toString() : "null"; - builder.append(" : arg[").append(i).append("] ").append(argClass); + catch (InvocationTargetException e) { + // Unwrap for HandlerExceptionResolvers ... + Throwable targetException = e.getTargetException(); + if (targetException instanceof RuntimeException) { + throw (RuntimeException) targetException; + } + else if (targetException instanceof Error) { + throw (Error) targetException; + } + else if (targetException instanceof Exception) { + throw (Exception) targetException; + } + else { + String msg = getInvocationErrorMessage("Failed to invoke controller method", args); + throw new IllegalStateException(msg, targetException); } } - else { - builder.append(" and 0 arguments"); - } - - throw new IllegalArgumentException(builder.toString(), ex); } - private void handleInvocationTargetException(InvocationTargetException ex) throws Exception { - Throwable targetException = ex.getTargetException(); - if (targetException instanceof RuntimeException) { - throw (RuntimeException) targetException; - } - if (targetException instanceof Error) { - throw (Error) targetException; - } - if (targetException instanceof Exception) { - throw (Exception) targetException; + private String getInvocationErrorMessage(String message, Object[] resolvedArgs) { + StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message)); + sb.append("Resolved arguments: \n"); + for (int i=0; i < resolvedArgs.length; i++) { + sb.append("[").append(i).append("] "); + if (resolvedArgs[i] == null) { + sb.append("[null] \n"); + } + else { + sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] "); + sb.append("[value=").append(resolvedArgs[i]).append("]\n"); + } } + return sb.toString(); } - + } \ No newline at end of file diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodArgumentResolverCompositeTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodArgumentResolverCompositeTests.java index 22940cab17..18a8a16416 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodArgumentResolverCompositeTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodArgumentResolverCompositeTests.java @@ -73,7 +73,7 @@ public class HandlerMethodArgumentResolverCompositeTests { assertEquals("Didn't use the first registered resolver", Integer.valueOf(1), resolvedValue); } - @Test(expected=IllegalStateException.class) + @Test(expected=IllegalArgumentException.class) public void noSuitableArgumentResolver() throws Exception { this.resolvers.resolveArgument(paramStr, null, null, null); } diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerCompositeTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerCompositeTests.java index ac1a6d6a04..99b53bb78b 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerCompositeTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerCompositeTests.java @@ -74,7 +74,7 @@ public class HandlerMethodReturnValueHandlerCompositeTests { assertNull("Shouldn't have use the 2nd registered handler", h2.getReturnValue()); } - @Test(expected=IllegalStateException.class) + @Test(expected=IllegalArgumentException.class) public void noSuitableReturnValueHandler() throws Exception { registerHandler(Integer.class); this.handlers.handleReturnValue("value", paramStr, null, null); diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java index 97b11b8dc0..0d3bafe735 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java @@ -17,89 +17,223 @@ package org.springframework.web.method.support; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.lang.reflect.Method; import org.junit.Before; import org.junit.Test; +import org.springframework.core.MethodParameter; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; /** - * Test fixture with {@link InvocableHandlerMethod}. + * Test fixture for {@link InvocableHandlerMethod} unit tests. * * @author Rossen Stoyanchev */ public class InvocableHandlerMethodTests { - private HandlerMethodArgumentResolverComposite argumentResolvers; + private InvocableHandlerMethod handleMethod; private NativeWebRequest webRequest; @Before public void setUp() throws Exception { - argumentResolvers = new HandlerMethodArgumentResolverComposite(); + Method method = Handler.class.getDeclaredMethod("handle", Integer.class, String.class); + this.handleMethod = new InvocableHandlerMethod(new Handler(), method); this.webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()); } @Test - public void resolveArgument() throws Exception { - StubArgumentResolver intResolver = addResolver(Integer.class, 99); - StubArgumentResolver strResolver = addResolver(String.class, "value"); - InvocableHandlerMethod method = invocableHandlerMethod("handle", Integer.class, String.class); - Object returnValue = method.invokeForRequest(webRequest, null); + public void resolveArg() throws Exception { + StubArgumentResolver intResolver = new StubArgumentResolver(Integer.class, 99); + StubArgumentResolver stringResolver = new StubArgumentResolver(String.class, "value"); + + HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); + composite.addResolver(intResolver); + composite.addResolver(stringResolver); + handleMethod.setHandlerMethodArgumentResolvers(composite); + + Object returnValue = handleMethod.invokeForRequest(webRequest, null); - assertEquals("Integer resolver not invoked", 1, intResolver.getResolvedParameters().size()); - assertEquals("String resolver not invoked", 1, strResolver.getResolvedParameters().size()); - assertEquals("Invalid return value", "99-value", returnValue); + assertEquals(1, intResolver.getResolvedParameters().size()); + assertEquals(1, stringResolver.getResolvedParameters().size()); + assertEquals("99-value", returnValue); + + assertEquals("intArg", intResolver.getResolvedParameters().get(0).getParameterName()); + assertEquals("stringArg", stringResolver.getResolvedParameters().get(0).getParameterName()); } - + @Test - public void resolveProvidedArgument() throws Exception { - InvocableHandlerMethod method = invocableHandlerMethod("handle", Integer.class, String.class); - Object returnValue = method.invokeForRequest(webRequest, null, 99, "value"); + public void resolveNullArg() throws Exception { + StubArgumentResolver intResolver = new StubArgumentResolver(Integer.class, null); + StubArgumentResolver stringResolver = new StubArgumentResolver(String.class, null); - assertEquals("Expected raw return value with no handlers registered", String.class, returnValue.getClass()); - assertEquals("Provided argument values were not resolved", "99-value", returnValue); + HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); + composite.addResolver(intResolver); + composite.addResolver(stringResolver); + handleMethod.setHandlerMethodArgumentResolvers(composite); + + Object returnValue = handleMethod.invokeForRequest(webRequest, null); + + assertEquals(1, intResolver.getResolvedParameters().size()); + assertEquals(1, stringResolver.getResolvedParameters().size()); + assertEquals("null-null", returnValue); } @Test - public void discoverParameterName() throws Exception { - StubArgumentResolver resolver = addResolver(Integer.class, 99); - InvocableHandlerMethod method = invocableHandlerMethod("parameterNameDiscovery", Integer.class); - method.invokeForRequest(webRequest, null); - - assertEquals("intArg", resolver.getResolvedParameters().get(0).getParameterName()); + public void cannotResolveArg() throws Exception { + try { + handleMethod.invokeForRequest(webRequest, null); + fail("Expected exception"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("No suitable resolver for argument [0] [type=java.lang.Integer]")); + } + } + + @Test + public void resolveProvidedArg() throws Exception { + Object returnValue = handleMethod.invokeForRequest(webRequest, null, 99, "value"); + + assertEquals(String.class, returnValue.getClass()); + assertEquals("99-value", returnValue); } - private StubArgumentResolver addResolver(Class parameterType, Object stubValue) { - StubArgumentResolver resolver = new StubArgumentResolver(parameterType, stubValue); - argumentResolvers.addResolver(resolver); - return resolver; + @Test + public void resolveProvidedArgFirst() throws Exception { + StubArgumentResolver intResolver = new StubArgumentResolver(Integer.class, 1); + StubArgumentResolver stringResolver = new StubArgumentResolver(String.class, "value1"); + + HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); + composite.addResolver(intResolver); + composite.addResolver(stringResolver); + handleMethod.setHandlerMethodArgumentResolvers(composite); + + Object returnValue = handleMethod.invokeForRequest(webRequest, null, 2, "value2"); + + assertEquals("2-value2", returnValue); } - private InvocableHandlerMethod invocableHandlerMethod(String methodName, Class... paramTypes) - throws Exception { - Method method = Handler.class.getDeclaredMethod(methodName, paramTypes); - InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(new Handler(), method); - handlerMethod.setHandlerMethodArgumentResolvers(argumentResolvers); - return handlerMethod; + @Test + public void exceptionInResolvingArg() throws Exception { + HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); + composite.addResolver(new ExceptionRaisingArgumentResolver()); + handleMethod.setHandlerMethodArgumentResolvers(composite); + + try { + handleMethod.invokeForRequest(webRequest, null); + fail("Expected exception"); + } catch (HttpMessageNotReadableException ex) { + // Expected.. + // Allow HandlerMethodArgumentResolver exceptions to propagate.. + } + } + + @Test + public void illegalArgumentException() throws Exception { + StubArgumentResolver intResolver = new StubArgumentResolver(Integer.class, "__invalid__"); + StubArgumentResolver stringResolver = new StubArgumentResolver(String.class, "value"); + + HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); + composite.addResolver(intResolver); + composite.addResolver(stringResolver); + handleMethod.setHandlerMethodArgumentResolvers(composite); + + try { + handleMethod.invokeForRequest(webRequest, null); + fail("Expected exception"); + } catch (IllegalArgumentException ex) { + assertNotNull("Exception not wrapped", ex.getCause()); + assertTrue(ex.getCause() instanceof IllegalArgumentException); + assertTrue(ex.getMessage().contains("Controller [")); + assertTrue(ex.getMessage().contains("Method [")); + assertTrue(ex.getMessage().contains("Resolved arguments: ")); + assertTrue(ex.getMessage().contains("[0] [type=java.lang.String] [value=__invalid__]")); + assertTrue(ex.getMessage().contains("[1] [type=java.lang.String] [value=value")); + } } + @Test + public void invocationTargetException() throws Exception { + Throwable expected = new RuntimeException("error"); + try { + invokeExceptionRaisingHandler(expected); + } catch (RuntimeException actual) { + assertSame(expected, actual); + } + + expected = new Error("error"); + try { + invokeExceptionRaisingHandler(expected); + } catch (Error actual) { + assertSame(expected, actual); + } + + expected = new Exception("error"); + try { + invokeExceptionRaisingHandler(expected); + } catch (Exception actual) { + assertSame(expected, actual); + } + + expected = new Throwable("error"); + try { + invokeExceptionRaisingHandler(expected); + } catch (IllegalStateException actual) { + assertNotNull(actual.getCause()); + assertSame(expected, actual.getCause()); + assertTrue(actual.getMessage().contains("Failed to invoke controller method")); + } + } + + private void invokeExceptionRaisingHandler(Throwable expected) throws Exception { + Method method = ExceptionRaisingHandler.class.getDeclaredMethod("raiseException"); + Object handler = new ExceptionRaisingHandler(expected); + new InvocableHandlerMethod(handler, method).invokeForRequest(webRequest, null); + fail("Expected exception"); + } + + @SuppressWarnings("unused") private static class Handler { - @SuppressWarnings("unused") public String handle(Integer intArg, String stringArg) { return intArg + "-" + stringArg; } - - @SuppressWarnings("unused") - @RequestMapping - public void parameterNameDiscovery(Integer intArg) { + } + + @SuppressWarnings("unused") + private static class ExceptionRaisingHandler { + + private final Throwable t; + + public ExceptionRaisingHandler(Throwable t) { + this.t = t; } + + public void raiseException() throws Throwable { + throw t; + } + } + + private static class ExceptionRaisingArgumentResolver implements HandlerMethodArgumentResolver { + + public boolean supportsParameter(MethodParameter parameter) { + return true; + } + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + throw new HttpMessageNotReadableException("oops, can't read"); + } + } + } diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/support/StubArgumentResolver.java b/org.springframework.web/src/test/java/org/springframework/web/method/support/StubArgumentResolver.java index c3c6a1904d..db89c8dea5 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/support/StubArgumentResolver.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/support/StubArgumentResolver.java @@ -24,7 +24,8 @@ import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; /** - * Resolves a method argument using a stub value and records resolved parameters. + * Supports parameters of a given type and resolves them using a stub value. + * Also records the resolved parameter value. * * @author Rossen Stoyanchev */ @@ -36,8 +37,8 @@ public class StubArgumentResolver implements HandlerMethodArgumentResolver { private List resolvedParameters = new ArrayList(); - public StubArgumentResolver(Class parameterType, Object stubValue) { - this.parameterType = parameterType; + public StubArgumentResolver(Class supportedParameterType, Object stubValue) { + this.parameterType = supportedParameterType; this.stubValue = stubValue; }