Browse Source

SPR-8447 Provide sufficient contextwherever possible when exceptions are raised in new @MVC classes.

pull/7/head
Rossen Stoyanchev 14 years ago
parent
commit
0dae1a6bd8
  1. 22
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
  2. 6
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java
  3. 11
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java
  4. 14
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java
  5. 6
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolver.java
  6. 16
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java
  7. 6
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java
  8. 8
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
  9. 81
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
  10. 4
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java
  11. 2
      org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java
  12. 3
      org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java
  13. 6
      org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java
  14. 5
      org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java
  15. 2
      org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java
  16. 5
      org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java
  17. 11
      org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java
  18. 11
      org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java
  19. 112
      org.springframework.web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java
  20. 2
      org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodArgumentResolverCompositeTests.java
  21. 2
      org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerCompositeTests.java
  22. 212
      org.springframework.web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java
  23. 7
      org.springframework.web/src/test/java/org/springframework/web/method/support/StubArgumentResolver.java

22
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 { @@ -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 { @@ -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.
*/

6
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 @@ -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);
}
}

11
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; @@ -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 @@ -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 @@ -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,

14
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 @@ -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()) {

6
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; @@ -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 @@ -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);
}
}

16
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; @@ -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 @@ -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);
}
}

6
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java

@ -16,6 +16,8 @@ @@ -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 @@ -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);
}
}

8
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java

@ -344,7 +344,7 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes @@ -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 @@ -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 @@ -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();
}

81
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java

@ -15,7 +15,7 @@ @@ -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; @@ -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 @@ -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 { @@ -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 { @@ -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 { @@ -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");
}
}
}

4
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java

@ -144,8 +144,6 @@ public class DefaultHandlerExceptionResolverTests { @@ -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 { @@ -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'"));
}
}

2
org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java

@ -64,7 +64,7 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { @@ -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);
}
}
}

3
org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java

@ -141,7 +141,8 @@ public final class ModelFactory { @@ -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);
}

6
org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java

@ -99,9 +99,9 @@ public abstract class AbstractWebArgumentResolverAdapter implements HandlerMetho @@ -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;
}

5
org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java

@ -56,8 +56,9 @@ public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolv @@ -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());
}
}

2
org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java

@ -66,7 +66,7 @@ public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMet @@ -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 {

5
org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java

@ -16,6 +16,7 @@ @@ -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 @@ -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);
}
}
}

11
org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java

@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; @@ -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 @@ -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);
}
/**

11
org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java

@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; @@ -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 @@ -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);
}
/**

112
org.springframework.web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java

@ -142,25 +142,52 @@ public class InvocableHandlerMethod extends HandlerMethod { @@ -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 { @@ -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();
}
}

2
org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodArgumentResolverCompositeTests.java

@ -73,7 +73,7 @@ public class HandlerMethodArgumentResolverCompositeTests { @@ -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);
}

2
org.springframework.web/src/test/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerCompositeTests.java

@ -74,7 +74,7 @@ public class HandlerMethodReturnValueHandlerCompositeTests { @@ -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);

212
org.springframework.web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java

@ -17,89 +17,223 @@ @@ -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");
}
}
}

7
org.springframework.web/src/test/java/org/springframework/web/method/support/StubArgumentResolver.java

@ -24,7 +24,8 @@ import org.springframework.web.bind.support.WebDataBinderFactory; @@ -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 { @@ -36,8 +37,8 @@ public class StubArgumentResolver implements HandlerMethodArgumentResolver {
private List<MethodParameter> resolvedParameters = new ArrayList<MethodParameter>();
public StubArgumentResolver(Class<?> parameterType, Object stubValue) {
this.parameterType = parameterType;
public StubArgumentResolver(Class<?> supportedParameterType, Object stubValue) {
this.parameterType = supportedParameterType;
this.stubValue = stubValue;
}

Loading…
Cancel
Save