Backport fixes in HandlerMethod and sub-classes

Issue: SPR-9747, SPR-9748, SPR-9218, SPR-8946, SPR-9159
Backport Issue: SPR-9622
This commit is contained in:
Rossen Stoyanchev
2012-09-10 14:56:09 -04:00
parent bec5463640
commit 229537288a
7 changed files with 356 additions and 298 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,6 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
@@ -57,17 +56,17 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.method.annotation.ErrorsMethodArgumentResolver;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.MapMethodProcessor;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.method.annotation.ModelFactory;
import org.springframework.web.method.annotation.ModelMethodProcessor;
import org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.annotation.SessionAttributesHandler;
import org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -84,9 +83,9 @@ import org.springframework.web.util.WebUtils;
/**
* An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s
* with the signature -- method argument and return types, defined in
* with the signature -- method argument and return types, defined in
* {@code @RequestMapping}.
*
*
* <p>Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
* Or alternatively to re-configure all argument and return value types use
@@ -103,7 +102,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
private List<ModelAndViewResolver> modelAndViewResolvers;
private List<HttpMessageConverter<?>> messageConverters;
@@ -115,20 +114,20 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private boolean synchronizeOnSession = false;
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private ConfigurableBeanFactory beanFactory;
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
private boolean ignoreDefaultModelOnRedirect = false;
private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache =
new ConcurrentHashMap<Class<?>, SessionAttributesHandler>();
private HandlerMethodArgumentResolverComposite argumentResolvers;
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
private final Map<Class<?>, Set<Method>> dataBinderFactoryCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
@@ -139,7 +138,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
* Default constructor.
*/
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
@@ -152,7 +151,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
/**
* Provide resolvers for custom argument types. Custom resolvers are ordered
* after built-in ones. To override the built-in support for argument
* after built-in ones. To override the built-in support for argument
* resolution use {@link #setArgumentResolvers} instead.
*/
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
@@ -179,9 +178,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
this.argumentResolvers.addResolvers(argumentResolvers);
}
}
/**
* Return the configured argument resolvers, or possibly {@code null} if
* Return the configured argument resolvers, or possibly {@code null} if
* not initialized yet via {@link #afterPropertiesSet()}.
*/
public HandlerMethodArgumentResolverComposite getArgumentResolvers() {
@@ -226,7 +225,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Configure the complete list of supported return value types thus
* Configure the complete list of supported return value types thus
* overriding handlers that would otherwise be configured by default.
*/
public void setReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
@@ -240,7 +239,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Return the configured handlers, or possibly {@code null} if not
* Return the configured handlers, or possibly {@code null} if not
* initialized yet via {@link #afterPropertiesSet()}.
*/
public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
@@ -248,16 +247,16 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Provide custom {@link ModelAndViewResolver}s.
* <p><strong>Note:</strong> This method is available for backwards
* compatibility only. However, it is recommended to re-write a
* Provide custom {@link ModelAndViewResolver}s.
* <p><strong>Note:</strong> This method is available for backwards
* compatibility only. However, it is recommended to re-write a
* {@code ModelAndViewResolver} as {@link HandlerMethodReturnValueHandler}.
* An adapter between the two interfaces is not possible since the
* An adapter between the two interfaces is not possible since the
* {@link HandlerMethodReturnValueHandler#supportsReturnType} method
* cannot be implemented. Hence {@code ModelAndViewResolver}s are limited
* to always being invoked at the end after all other return value
* to always being invoked at the end after all other return value
* handlers have been given a chance.
* <p>A {@code HandlerMethodReturnValueHandler} provides better access to
* <p>A {@code HandlerMethodReturnValueHandler} provides better access to
* the return type and controller method information and can be ordered
* freely relative to other return value handlers.
*/
@@ -273,8 +272,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Provide the converters to use in argument resolvers and return value
* handlers that support reading and/or writing to the body of the
* Provide the converters to use in argument resolvers and return value
* handlers that support reading and/or writing to the body of the
* request and response.
*/
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
@@ -289,7 +288,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Provide a WebBindingInitializer with "global" initialization to apply
* Provide a WebBindingInitializer with "global" initialization to apply
* to every DataBinder instance.
*/
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
@@ -304,20 +303,20 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Specify the strategy to store session attributes with. The default is
* Specify the strategy to store session attributes with. The default is
* {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the HttpSession with the same attribute
* storing session attributes in the HttpSession with the same attribute
* name as in the model.
*/
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
this.sessionAttributeStore = sessionAttributeStore;
}
/**
* Cache content produced by <code>@SessionAttributes</code> annotated handlers
* for the given number of seconds. Default is 0, preventing caching completely.
* <p>In contrast to the "cacheSeconds" property which will apply to all general
* handlers (but not to <code>@SessionAttributes</code> annotated handlers),
* <p>In contrast to the "cacheSeconds" property which will apply to all general
* handlers (but not to <code>@SessionAttributes</code> annotated handlers),
* this setting will apply to <code>@SessionAttributes</code> handlers only.
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
@@ -349,25 +348,25 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Set the ParameterNameDiscoverer to use for resolving method parameter
* names if needed (e.g. for default attribute names). Default is a
* Set the ParameterNameDiscoverer to use for resolving method parameter
* names if needed (e.g. for default attribute names). Default is a
* {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* By default the content of the "default" model is used both during
* rendering and redirect scenarios. Alternatively a controller method
* By default the content of the "default" model is used both during
* rendering and redirect scenarios. Alternatively a controller method
* can declare a {@link RedirectAttributes} argument and use it to provide
* attributes for a redirect.
* <p>Setting this flag to {@code true} guarantees the "default" model is
* never used in a redirect scenario even if a RedirectAttributes argument
* is not declared. Setting it to {@code false} means the "default" model
* may be used in a redirect if the controller method doesn't declare a
* <p>Setting this flag to {@code true} guarantees the "default" model is
* never used in a redirect scenario even if a RedirectAttributes argument
* is not declared. Setting it to {@code false} means the "default" model
* may be used in a redirect if the controller method doesn't declare a
* RedirectAttributes argument.
* <p>The default setting is {@code false} but new applications should
* <p>The default setting is {@code false} but new applications should
* consider setting it to {@code true}.
* @see RedirectAttributes
*/
@@ -377,7 +376,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
/**
* {@inheritDoc}
* <p>A {@link ConfigurableBeanFactory} is expected for resolving
* <p>A {@link ConfigurableBeanFactory} is expected for resolving
* expressions in method argument default values.
*/
public void setBeanFactory(BeanFactory beanFactory) {
@@ -387,7 +386,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Return the owning factory of this bean instance, or {@code null}.
* Return the owning factory of this bean instance, or {@code null}.
*/
protected ConfigurableBeanFactory getBeanFactory() {
return this.beanFactory;
@@ -451,7 +450,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Return the list of argument resolvers to use for {@code @InitBinder}
* Return the list of argument resolvers to use for {@code @InitBinder}
* methods including built-in and custom resolvers.
*/
private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
@@ -474,12 +473,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
return resolvers;
}
/**
* Return the list of return value handlers to use including built-in and
* Return the list of return value handlers to use including built-in and
* custom handlers provided via {@link #setReturnValueHandlers}.
*/
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
@@ -516,33 +515,22 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Return {@code true} if all arguments and the return value of the given
* HandlerMethod are supported by the configured resolvers and handlers.
* Always return {@code true} since any method argument and return value
* type will be processed in some way. A method argument not recognized
* by any HandlerMethodArgumentResolver is interpreted as a request parameter
* if it is a simple type, or as a model attribute otherwise. A return value
* not recognized by any HandlerMethodReturnValueHandler will be interpreted
* as a model attribute.
*/
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return supportsMethodParameters(handlerMethod.getMethodParameters()) &&
supportsReturnType(handlerMethod.getReturnType());
}
private boolean supportsMethodParameters(MethodParameter[] methodParameters) {
for (MethodParameter methodParameter : methodParameters) {
if (! this.argumentResolvers.supportsParameter(methodParameter)) {
return false;
}
}
return true;
}
private boolean supportsReturnType(MethodParameter methodReturnType) {
return (this.returnValueHandlers.supportsReturnType(methodReturnType) ||
Void.TYPE.equals(methodReturnType.getParameterType()));
}
/**
* This implementation always returns -1. An {@code @RequestMapping}
* method can calculate the lastModified value, call
* {@link WebRequest#checkNotModified(long)}, and return {@code null}
* This implementation always returns -1. An {@code @RequestMapping}
* method can calculate the lastModified value, call
* {@link WebRequest#checkNotModified(long)}, and return {@code null}
* if the result of that call is {@code true}.
*/
@Override
@@ -552,8 +540,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
@Override
protected final ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
// Always prevent caching in case of session attribute management.
@@ -563,7 +550,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
// Uses configured default cacheSeconds setting.
checkAndPrepare(request, response, true);
}
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
@@ -574,12 +561,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
}
}
return invokeHandlerMethod(request, response, handlerMethod);
}
/**
* Return the {@link SessionAttributesHandler} instance for the given
* Return the {@link SessionAttributesHandler} instance for the given
* handler type, never {@code null}.
*/
private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
@@ -602,7 +589,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
*/
private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
@@ -634,17 +621,17 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
}
private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod,
private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod,
WebDataBinderFactory binderFactory) {
ServletInvocableHandlerMethod requestMethod;
requestMethod = new ServletInvocableHandlerMethod(handlerMethod.getBean(), handlerMethod.getMethod());
requestMethod = new ServletInvocableHandlerMethod(handlerMethod);
requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
requestMethod.setDataBinderFactory(binderFactory);
requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
return requestMethod;
}
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
Class<?> handlerType = handlerMethod.getBeanType();
@@ -692,6 +679,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
*/
protected ServletRequestDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
throws Exception {
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}

View File

@@ -22,9 +22,8 @@ import java.lang.reflect.Method;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
@@ -32,16 +31,19 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
/**
* Extends {@link InvocableHandlerMethod} with the ability to handle the value returned from the method through
* a registered {@link HandlerMethodArgumentResolver} that supports the given return value type.
* Return value handling may include writing to the response or updating the {@link ModelAndViewContainer} structure.
* Extends {@link InvocableHandlerMethod} with the ability to handle return
* values through a registered {@link HandlerMethodReturnValueHandler} and
* also supports setting the response status based on a method-level
* {@code @ResponseStatus} annotation.
*
* <p>If the underlying method has a {@link ResponseStatus} instruction, the status on the response is set
* accordingly after the method is invoked but before the return value is handled.
* <p>A {@code null} return value (including void) may be interpreted as the
* end of request processing in combination with a {@code @ResponseStatus}
* annotation, a not-modified check condition
* (see {@link ServletWebRequest#checkNotModified(long)}), or
* a method argument that provides access to the response stream.
*
* @author Rossen Stoyanchev
* @since 3.1
* @see #invokeAndHandle(NativeWebRequest, ModelAndViewContainer, Object...)
*/
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
@@ -51,64 +53,71 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
/**
* Creates an instance from the given handler and method.
*/
public ServletInvocableHandlerMethod(Object handler, Method method) {
super(handler, method);
initResponseStatus();
}
/**
* Create an instance from a {@code HandlerMethod}.
*/
public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
initResponseStatus();
}
private void initResponseStatus() {
ResponseStatus annot = getMethodAnnotation(ResponseStatus.class);
if (annot != null) {
this.responseStatus = annot.value();
this.responseReason = annot.reason();
}
}
/**
* Register {@link HandlerMethodReturnValueHandler} instances to use to
* handle return values.
*/
public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
this.returnValueHandlers = returnValueHandlers;
}
/**
* Creates a {@link ServletInvocableHandlerMethod} instance with the given bean and method.
* @param handler the object handler
* @param method the method
*/
public ServletInvocableHandlerMethod(Object handler, Method method) {
super(handler, method);
ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
if (annotation != null) {
this.responseStatus = annotation.value();
this.responseReason = annotation.reason();
}
}
/**
* Invokes the method and handles the return value through a registered {@link HandlerMethodReturnValueHandler}.
* <p>Return value handling may be skipped entirely when the method returns {@code null} (also possibly due
* to a {@code void} return type) and one of the following additional conditions is true:
* <ul>
* <li>A {@link HandlerMethodArgumentResolver} has set the {@link ModelAndViewContainer#setRequestHandled(boolean)}
* flag to {@code false} -- e.g. method arguments providing access to the response.
* <li>The request qualifies as "not modified" as defined in {@link ServletWebRequest#checkNotModified(long)}
* and {@link ServletWebRequest#checkNotModified(String)}. In this case a response with "not modified" response
* headers will be automatically generated without the need for return value handling.
* <li>The status on the response is set due to a @{@link ResponseStatus} instruction.
* </ul>
* <p>After the return value is handled, callers of this method can use the {@link ModelAndViewContainer}
* to gain access to model attributes, view selection choices, and to check if view resolution is even needed.
* Invokes the method and handles the return value through a registered
* {@link HandlerMethodReturnValueHandler}.
*
* @param request the current request
* @param mavContainer the {@link ModelAndViewContainer} for the current request
* @param providedArgs argument values to try to use without the need for view resolution
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type, not resolved
*/
public final void invokeAndHandle(
NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
public final void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(request, mavContainer, providedArgs);
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus((ServletWebRequest) request);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(request) || hasResponseStatus() || mavContainer.isRequestHandled()) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
} catch (Exception ex) {
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
@@ -116,6 +125,41 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
}
}
/**
* Set the response status according to the {@link ResponseStatus} annotation.
*/
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
if (this.responseStatus == null) {
return;
}
if (StringUtils.hasText(this.responseReason)) {
webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
}
else {
webRequest.getResponse().setStatus(this.responseStatus.value());
}
// to be picked up by the RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
}
/**
* Does the given request qualify as "not modified"?
* @see ServletWebRequest#checkNotModified(long)
* @see ServletWebRequest#checkNotModified(String)
*/
private boolean isRequestNotModified(ServletWebRequest webRequest) {
return webRequest.isNotModified();
}
/**
* Does this method have the response status instruction?
*/
private boolean hasResponseStatus() {
return responseStatus != null;
}
private String getReturnValueHandlingErrorMessage(String message, Object returnValue) {
StringBuilder sb = new StringBuilder(message);
if (returnValue != null) {
@@ -125,36 +169,4 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
return getDetailedErrorMessage(sb.toString());
}
/**
* Set the response status according to the {@link ResponseStatus} annotation.
*/
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
if (this.responseStatus != null) {
if (StringUtils.hasText(this.responseReason)) {
webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
}
else {
webRequest.getResponse().setStatus(this.responseStatus.value());
}
// to be picked up by the RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
}
}
/**
* Does the given request qualify as "not modified"?
* @see ServletWebRequest#checkNotModified(long)
* @see ServletWebRequest#checkNotModified(String)
*/
private boolean isRequestNotModified(NativeWebRequest request) {
return ((ServletWebRequest) request).isNotModified();
}
/**
* Does this method have the response status instruction?
*/
private boolean hasResponseStatus() {
return responseStatus != null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,10 @@
*/
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;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
@@ -30,17 +31,20 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.view.RedirectView;
/**
* Test fixture with {@link ServletInvocableHandlerMethod}.
*
*
* @author Rossen Stoyanchev
*/
public class ServletInvocableHandlerMethodTests {
@@ -53,6 +57,8 @@ public class ServletInvocableHandlerMethodTests {
private ServletWebRequest webRequest;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
@@ -60,24 +66,23 @@ public class ServletInvocableHandlerMethodTests {
returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
argumentResolvers = new HandlerMethodArgumentResolverComposite();
mavContainer = new ModelAndViewContainer();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);
webRequest = new ServletWebRequest(request, response);
}
@Test
public void nullReturnValueResponseStatus() throws Exception {
public void invokeAndHandle_VoidWithResponseStatus() throws Exception {
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatus");
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("Null return value + @ResponseStatus should result in 'request handled'",
mavContainer.isRequestHandled());
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
assertEquals("400 Bad Request", response.getErrorMessage());
}
@Test
public void nullReturnValueHttpServletResponseArg() throws Exception {
public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception {
argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("httpServletResponse", HttpServletResponse.class);
@@ -88,33 +93,61 @@ public class ServletInvocableHandlerMethodTests {
}
@Test
public void nullReturnValueRequestNotModified() throws Exception {
public void invokeAndHandle_VoidRequestNotModified() throws Exception {
webRequest.getNativeRequest(MockHttpServletRequest.class).addHeader("If-Modified-Since", 10 * 1000 * 1000);
int lastModifiedTimestamp = 1000 * 1000;
webRequest.checkNotModified(lastModifiedTimestamp);
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("notModified");
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("Null return value + 'not modified' request should result in 'request handled'",
mavContainer.isRequestHandled());
}
// SPR-9159
@Test
public void exceptionWhileHandlingReturnValue() throws Exception {
public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exception {
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatusWithReason");
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("When a phrase is used, the response should not be used any more", mavContainer.isRequestHandled());
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
assertEquals("400 Bad Request", response.getErrorMessage());
}
@Test(expected=HttpMessageNotWritableException.class)
public void invokeAndHandle_Exception() 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..
}
handlerMethod.invokeAndHandle(webRequest, mavContainer);
fail("Expected exception");
}
private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class<?>... argTypes)
@Test
public void invokeAndHandle_DynamicReturnValue() throws Exception {
argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(null, false));
returnValueHandlers.addHandler(new ViewMethodReturnValueHandler());
returnValueHandlers.addHandler(new ViewNameMethodReturnValueHandler());
// Invoke without a request parameter (String return value)
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("dynamicReturnValue", String.class);
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertNotNull(mavContainer.getView());
assertEquals(RedirectView.class, mavContainer.getView().getClass());
// Invoke with a request parameter (RedirectView return value)
request.setParameter("param", "value");
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertEquals("view", mavContainer.getViewName());
}
private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class<?>... argTypes)
throws NoSuchMethodException {
Method method = Handler.class.getDeclaredMethod(methodName, argTypes);
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Handler(), method);
@@ -130,16 +163,24 @@ public class ServletInvocableHandlerMethodTests {
return "view";
}
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public void responseStatus() {
}
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
public String responseStatusWithReason() {
return "foo";
}
public void httpServletResponse(HttpServletResponse response) {
}
public void notModified() {
}
public Object dynamicReturnValue(@RequestParam(required=false) String param) {
return (param != null) ? "view" : new RedirectView("redirectView");
}
}
private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler {