SPR-5624 - A default HandlerExceptionResolver that resolves standard Spring exceptions

This commit is contained in:
Arjen Poutsma 2009-03-27 19:53:42 +00:00
parent f92b7f1011
commit 161c926054
9 changed files with 1094 additions and 780 deletions

View File

@ -22,7 +22,6 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@ -48,7 +47,6 @@ import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.ui.context.ThemeSource;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
@ -57,88 +55,65 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
/**
* Central dispatcher for HTTP request handlers/controllers,
* e.g. for web UI controllers or HTTP-based remote service exporters.
* Dispatches to registered handlers for processing a web request,
* providing convenient mapping and exception handling facilities.
* Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service
* exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception
* handling facilities.
*
* <p>This servlet is very flexible: It can be used with just about any workflow,
* with the installation of the appropriate adapter classes. It offers the
* following functionality that distinguishes it from other request-driven
* <p>This servlet is very flexible: It can be used with just about any workflow, with the installation of the
* appropriate adapter classes. It offers the following functionality that distinguishes it from other request-driven
* web MVC frameworks:
*
* <ul>
* <li>It is based around a JavaBeans configuration mechanism.
* <ul> <li>It is based around a JavaBeans configuration mechanism.
*
* <li>It can use any {@link HandlerMapping} implementation - pre-built or provided
* as part of an application - to control the routing of requests to handler objects.
* Default is {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} and
* {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}.
* HandlerMapping objects can be defined as beans in the servlet's application context,
* implementing the HandlerMapping interface, overriding the default HandlerMapping if present.
* HandlerMappings can be given any bean name (they are tested by type).
* <li>It can use any {@link HandlerMapping} implementation - pre-built or provided as part of an application - to
* control the routing of requests to handler objects. Default is {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
* and {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}. HandlerMapping objects
* can be defined as beans in the servlet's application context, implementing the HandlerMapping interface, overriding
* the default HandlerMapping if present. HandlerMappings can be given any bean name (they are tested by type).
*
* <li>It can use any {@link HandlerAdapter}; this allows for using any handler interface.
* Default adapters are {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter},
* {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter},
* for Spring's {@link org.springframework.web.HttpRequestHandler} and
* {@link org.springframework.web.servlet.mvc.Controller} interfaces, respectively. A default
* {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
* will be registered as well. HandlerAdapter objects can be added as beans in the
* application context, overriding the default HandlerAdapters. Like HandlerMappings,
* HandlerAdapters can be given any bean name (they are tested by type).
* <li>It can use any {@link HandlerAdapter}; this allows for using any handler interface. Default adapters are {@link
* org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter}, {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter},
* for Spring's {@link org.springframework.web.HttpRequestHandler} and {@link org.springframework.web.servlet.mvc.Controller}
* interfaces, respectively. A default {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
* will be registered as well. HandlerAdapter objects can be added as beans in the application context, overriding the
* default HandlerAdapters. Like HandlerMappings, HandlerAdapters can be given any bean name (they are tested by type).
*
* <li>The dispatcher's exception resolution strategy can be specified via a
* {@link HandlerExceptionResolver}, for example mapping certain exceptions to
* error pages. Default is none. Additional HandlerExceptionResolvers can be added
* through the application context. HandlerExceptionResolver can be given any
* bean name (they are tested by type).
* <li>The dispatcher's exception resolution strategy can be specified via a {@link HandlerExceptionResolver}, for
* example mapping certain exceptions to error pages. Default is none. Additional HandlerExceptionResolvers can be added
* through the application context. HandlerExceptionResolver can be given any bean name (they are tested by type).
*
* <li>Its view resolution strategy can be specified via a {@link ViewResolver}
* implementation, resolving symbolic view names into View objects. Default is
* {@link org.springframework.web.servlet.view.InternalResourceViewResolver}.
* ViewResolver objects can be added as beans in the application context,
* overriding the default ViewResolver. ViewResolvers can be given any bean name
* (they are tested by type).
* <li>Its view resolution strategy can be specified via a {@link ViewResolver} implementation, resolving symbolic view
* names into View objects. Default is {@link org.springframework.web.servlet.view.InternalResourceViewResolver}.
* ViewResolver objects can be added as beans in the application context, overriding the default ViewResolver.
* ViewResolvers can be given any bean name (they are tested by type).
*
* <li>If a {@link View} or view name is not supplied by the user, then the configured
* {@link RequestToViewNameTranslator} will translate the current request into a
* view name. The corresponding bean name is "viewNameTranslator"; the default is
* {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}.
* <li>If a {@link View} or view name is not supplied by the user, then the configured {@link
* RequestToViewNameTranslator} will translate the current request into a view name. The corresponding bean name is
* "viewNameTranslator"; the default is {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}.
*
* <li>The dispatcher's strategy for resolving multipart requests is determined by
* a {@link org.springframework.web.multipart.MultipartResolver} implementation.
* Implementations for Jakarta Commons FileUpload and Jason Hunter's COS are
* included; the typical choise is
* {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
* <li>The dispatcher's strategy for resolving multipart requests is determined by a {@link
* org.springframework.web.multipart.MultipartResolver} implementation. Implementations for Jakarta Commons FileUpload
* and Jason Hunter's COS are included; the typical choise is {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
* The MultipartResolver bean name is "multipartResolver"; default is none.
*
* <li>Its locale resolution strategy is determined by a {@link LocaleResolver}.
* Out-of-the-box implementations work via HTTP accept header, cookie, or session.
* The LocaleResolver bean name is "localeResolver"; default is
* {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}.
* <li>Its locale resolution strategy is determined by a {@link LocaleResolver}. Out-of-the-box implementations work via
* HTTP accept header, cookie, or session. The LocaleResolver bean name is "localeResolver"; default is {@link
* org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}.
*
* <li>Its theme resolution strategy is determined by a {@link ThemeResolver}.
* Implementations for a fixed theme and for cookie and session storage are included.
* The ThemeResolver bean name is "themeResolver"; default is
* {@link org.springframework.web.servlet.theme.FixedThemeResolver}.
* </ul>
* <li>Its theme resolution strategy is determined by a {@link ThemeResolver}. Implementations for a fixed theme and for
* cookie and session storage are included. The ThemeResolver bean name is "themeResolver"; default is {@link
* org.springframework.web.servlet.theme.FixedThemeResolver}. </ul>
*
* <p><b>NOTE: The <code>@RequestMapping</code> annotation will only be processed
* if a corresponding <code>HandlerMapping</code> (for type level annotations)
* and/or <code>HandlerAdapter</code> (for method level annotations)
* is present in the dispatcher.</b> This is the case by default.
* However, if you are defining custom <code>HandlerMappings</code> or
* <code>HandlerAdapters</code>, then you need to make sure that a
* corresponding custom <code>DefaultAnnotationHandlerMapping</code>
* and/or <code>AnnotationMethodHandlerAdapter</code> is defined as well
* - provided that you intend to use <code>@RequestMapping</code>.
* <p><b>NOTE: The <code>@RequestMapping</code> annotation will only be processed if a corresponding
* <code>HandlerMapping</code> (for type level annotations) and/or <code>HandlerAdapter</code> (for method level
* annotations) is present in the dispatcher.</b> This is the case by default. However, if you are defining custom
* <code>HandlerMappings</code> or <code>HandlerAdapters</code>, then you need to make sure that a corresponding custom
* <code>DefaultAnnotationHandlerMapping</code> and/or <code>AnnotationMethodHandlerAdapter</code> is defined as well -
* provided that you intend to use <code>@RequestMapping</code>.
*
* <p><b>A web application can define any number of DispatcherServlets.</b>
* Each servlet will operate in its own namespace, loading its own application
* context with mappings, handlers, etc. Only the root application context
* as loaded by {@link org.springframework.web.context.ContextLoaderListener},
* if any, will be shared.
* <p><b>A web application can define any number of DispatcherServlets.</b> Each servlet will operate in its own
* namespace, loading its own application context with mappings, handlers, etc. Only the root application context as
* loaded by {@link org.springframework.web.context.ContextLoaderListener}, if any, will be shared.
*
* @author Rod Johnson
* @author Juergen Hoeller
@ -149,102 +124,92 @@ import org.springframework.web.util.WebUtils;
*/
public class DispatcherServlet extends FrameworkServlet {
/**
* Well-known name for the MultipartResolver object in the bean factory for this namespace.
*/
/** Well-known name for the MultipartResolver object in the bean factory for this namespace. */
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
/**
* Well-known name for the LocaleResolver object in the bean factory for this namespace.
*/
/** Well-known name for the LocaleResolver object in the bean factory for this namespace. */
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
/**
* Well-known name for the ThemeResolver object in the bean factory for this namespace.
*/
/** Well-known name for the ThemeResolver object in the bean factory for this namespace. */
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
/**
* Well-known name for the HandlerMapping object in the bean factory for this namespace.
* Only used when "detectAllHandlerMappings" is turned off.
* Well-known name for the HandlerMapping object in the bean factory for this namespace. Only used when
* "detectAllHandlerMappings" is turned off.
*
* @see #setDetectAllHandlerMappings
*/
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
/**
* Well-known name for the HandlerAdapter object in the bean factory for this namespace.
* Only used when "detectAllHandlerAdapters" is turned off.
* Well-known name for the HandlerAdapter object in the bean factory for this namespace. Only used when
* "detectAllHandlerAdapters" is turned off.
*
* @see #setDetectAllHandlerAdapters
*/
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
/**
* Well-known name for the HandlerExceptionResolver object in the bean factory for this
* namespace. Only used when "detectAllHandlerExceptionResolvers" is turned off.
* Well-known name for the HandlerExceptionResolver object in the bean factory for this namespace. Only used when
* "detectAllHandlerExceptionResolvers" is turned off.
*
* @see #setDetectAllHandlerExceptionResolvers
*/
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
/**
* Well-known name for the RequestToViewNameTranslator object in the bean factory for
* this namespace.
*/
/** Well-known name for the RequestToViewNameTranslator object in the bean factory for this namespace. */
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
/**
* Well-known name for the ViewResolver object in the bean factory for this namespace.
* Only used when "detectAllViewResolvers" is turned off.
* Well-known name for the ViewResolver object in the bean factory for this namespace. Only used when
* "detectAllViewResolvers" is turned off.
*
* @see #setDetectAllViewResolvers
*/
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
/**
* Request attribute to hold the currently chosen HandlerExecutionChain.
* Only used for internal optimizations.
*/
/** Request attribute to hold the currently chosen HandlerExecutionChain. Only used for internal optimizations. */
public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class.getName() + ".HANDLER";
/**
* Request attribute to hold the current web application context.
* Otherwise only the global web app context is obtainable by tags etc.
* Request attribute to hold the current web application context. Otherwise only the global web app context is
* obtainable by tags etc.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getWebApplicationContext
*/
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
/**
* Request attribute to hold the current LocaleResolver, retrievable by views.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocaleResolver
*/
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
/**
* Request attribute to hold the current ThemeResolver, retrievable by views.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getThemeResolver
*/
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
/**
* Request attribute to hold the current ThemeSource, retrievable by views.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource
*/
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
/**
* Log category to use when no mapped handler is found for a request.
*/
/** Log category to use when no mapped handler is found for a request. */
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Name of the class path resource (relative to the DispatcherServlet class)
* that defines DispatcherServlet's default strategy names.
* Name of the class path resource (relative to the DispatcherServlet class) that defines DispatcherServlet's default
* strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
/**
* Additional logger to use when no mapped handler is found for a request.
*/
/** Additional logger to use when no mapped handler is found for a request. */
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
private static final Properties defaultStrategies;
@ -262,7 +227,6 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
private boolean detectAllHandlerMappings = true;
@ -278,7 +242,6 @@ public class DispatcherServlet extends FrameworkServlet {
/** Perform cleanup of request attributes after include request? */
private boolean cleanupAfterInclude = true;
/** MultipartResolver used by this servlet */
private MultipartResolver multipartResolver;
@ -303,80 +266,65 @@ public class DispatcherServlet extends FrameworkServlet {
/** List of ViewResolvers used by this servlet */
private List<ViewResolver> viewResolvers;
/**
* Set whether to detect all HandlerMapping beans in this servlet's context.
* Else, just a single bean with name "handlerMapping" will be expected.
* <p>Default is "true". Turn this off if you want this servlet to use a
* single HandlerMapping, despite multiple HandlerMapping beans being
* defined in the context.
* Set whether to detect all HandlerMapping beans in this servlet's context. Else, just a single bean with name
* "handlerMapping" will be expected. <p>Default is "true". Turn this off if you want this servlet to use a single
* HandlerMapping, despite multiple HandlerMapping beans being defined in the context.
*/
public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) {
this.detectAllHandlerMappings = detectAllHandlerMappings;
}
/**
* Set whether to detect all HandlerAdapter beans in this servlet's context.
* Else, just a single bean with name "handlerAdapter" will be expected.
* <p>Default is "true". Turn this off if you want this servlet to use a
* single HandlerAdapter, despite multiple HandlerAdapter beans being
* defined in the context.
* Set whether to detect all HandlerAdapter beans in this servlet's context. Else, just a single bean with name
* "handlerAdapter" will be expected. <p>Default is "true". Turn this off if you want this servlet to use a single
* HandlerAdapter, despite multiple HandlerAdapter beans being defined in the context.
*/
public void setDetectAllHandlerAdapters(boolean detectAllHandlerAdapters) {
this.detectAllHandlerAdapters = detectAllHandlerAdapters;
}
/**
* Set whether to detect all HandlerExceptionResolver beans in this servlet's context.
* Else, just a single bean with name "handlerExceptionResolver" will be expected.
* <p>Default is "true". Turn this off if you want this servlet to use a
* single HandlerExceptionResolver, despite multiple HandlerExceptionResolver
* beans being defined in the context.
* Set whether to detect all HandlerExceptionResolver beans in this servlet's context. Else, just a single bean with
* name "handlerExceptionResolver" will be expected. <p>Default is "true". Turn this off if you want this servlet to
* use a single HandlerExceptionResolver, despite multiple HandlerExceptionResolver beans being defined in the
* context.
*/
public void setDetectAllHandlerExceptionResolvers(boolean detectAllHandlerExceptionResolvers) {
this.detectAllHandlerExceptionResolvers = detectAllHandlerExceptionResolvers;
}
/**
* Set whether to detect all ViewResolver beans in this servlet's context.
* Else, just a single bean with name "viewResolver" will be expected.
* <p>Default is "true". Turn this off if you want this servlet to use a
* single ViewResolver, despite multiple ViewResolver beans being
* defined in the context.
* Set whether to detect all ViewResolver beans in this servlet's context. Else, just a single bean with name
* "viewResolver" will be expected. <p>Default is "true". Turn this off if you want this servlet to use a single
* ViewResolver, despite multiple ViewResolver beans being defined in the context.
*/
public void setDetectAllViewResolvers(boolean detectAllViewResolvers) {
this.detectAllViewResolvers = detectAllViewResolvers;
}
/**
* Set whether to perform cleanup of request attributes after an include request,
* that is, whether to reset the original state of all request attributes after
* the DispatcherServlet has processed within an include request. Else, just the
* DispatcherServlet's own request attributes will be reset, but not model
* attributes for JSPs or special attributes set by views (for example, JSTL's).
* <p>Default is "true", which is strongly recommended. Views should not rely on
* request attributes having been set by (dynamic) includes. This allows JSP views
* rendered by an included controller to use any model attributes, even with the
* same names as in the main JSP, without causing side effects. Only turn this
* off for special needs, for example to deliberately allow main JSPs to access
* attributes from JSP views rendered by an included controller.
* Set whether to perform cleanup of request attributes after an include request, that is, whether to reset the
* original state of all request attributes after the DispatcherServlet has processed within an include request. Else,
* just the DispatcherServlet's own request attributes will be reset, but not model attributes for JSPs or special
* attributes set by views (for example, JSTL's). <p>Default is "true", which is strongly recommended. Views should not
* rely on request attributes having been set by (dynamic) includes. This allows JSP views rendered by an included
* controller to use any model attributes, even with the same names as in the main JSP, without causing side effects.
* Only turn this off for special needs, for example to deliberately allow main JSPs to access attributes from JSP
* views rendered by an included controller.
*/
public void setCleanupAfterInclude(boolean cleanupAfterInclude) {
this.cleanupAfterInclude = cleanupAfterInclude;
}
/**
* This implementation calls {@link #initStrategies}.
*/
/** This implementation calls {@link #initStrategies}. */
@Override
protected void onRefresh(ApplicationContext context) throws BeansException {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize
* Initialize the strategy objects that this servlet uses. <p>May be overridden in subclasses in order to initialize
* further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
@ -391,8 +339,7 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Initialize the MultipartResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory
* Initialize the MultipartResolver used by this class. <p>If no bean is defined with the given name in the BeanFactory
* for this namespace, no multipart handling is provided.
*/
private void initMultipartResolver(ApplicationContext context) {
@ -406,15 +353,14 @@ public class DispatcherServlet extends FrameworkServlet {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}
/**
* Initialize the LocaleResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory
* Initialize the LocaleResolver used by this class. <p>If no bean is defined with the given name in the BeanFactory
* for this namespace, we default to AcceptHeaderLocaleResolver.
*/
private void initLocaleResolver(ApplicationContext context) {
@ -435,9 +381,8 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Initialize the ThemeResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory
* for this namespace, we default to a FixedThemeResolver.
* Initialize the ThemeResolver used by this class. <p>If no bean is defined with the given name in the BeanFactory for
* this namespace, we default to a FixedThemeResolver.
*/
private void initThemeResolver(ApplicationContext context) {
try {
@ -450,24 +395,24 @@ public class DispatcherServlet extends FrameworkServlet {
// We need to use the default.
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME +
"': using default [" + this.themeResolver + "]");
logger.debug(
"Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" +
this.themeResolver + "]");
}
}
}
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory
* for this namespace, we default to BeanNameUrlHandlerMapping.
* Initialize the HandlerMappings used by this class. <p>If no HandlerMapping beans are defined in the BeanFactory for
* this namespace, we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
@ -495,17 +440,16 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Initialize the HandlerAdapters used by this class.
* <p>If no HandlerAdapter beans are defined in the BeanFactory
* for this namespace, we default to SimpleControllerHandlerAdapter.
* Initialize the HandlerAdapters used by this class. <p>If no HandlerAdapter beans are defined in the BeanFactory for
* this namespace, we default to SimpleControllerHandlerAdapter.
*/
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerAdapter.class, true, false);
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
@ -533,17 +477,16 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Initialize the HandlerExceptionResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory
* for this namespace, we default to no exception resolver.
* Initialize the HandlerExceptionResolver used by this class. <p>If no bean is defined with the given name in the
* BeanFactory for this namespace, we default to no exception resolver.
*/
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerExceptionResolver.class, true, false);
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
@ -552,8 +495,8 @@ public class DispatcherServlet extends FrameworkServlet {
}
else {
try {
HandlerExceptionResolver her = context.getBean(
HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
@ -561,8 +504,8 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
// Just for consistency, check for default HandlerExceptionResolvers...
// There aren't any in usual scenarios.
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isDebugEnabled()) {
@ -572,13 +515,13 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Initialize the RequestToViewNameTranslator used by this servlet instance. If no
* implementation is configured then we default to DefaultRequestToViewNameTranslator.
* Initialize the RequestToViewNameTranslator used by this servlet instance. If no implementation is configured then we
* default to DefaultRequestToViewNameTranslator.
*/
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
this.viewNameTranslator = context.getBean(
REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
this.viewNameTranslator =
context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
if (logger.isDebugEnabled()) {
logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
}
@ -588,24 +531,23 @@ public class DispatcherServlet extends FrameworkServlet {
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
"': using default [" + this.viewNameTranslator + "]");
REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default [" + this.viewNameTranslator +
"]");
}
}
}
/**
* Initialize the ViewResolvers used by this class.
* <p>If no ViewResolver beans are defined in the BeanFactory
* for this namespace, we default to InternalResourceViewResolver.
* Initialize the ViewResolvers used by this class. <p>If no ViewResolver beans are defined in the BeanFactory for this
* namespace, we default to InternalResourceViewResolver.
*/
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, ViewResolver.class, true, false);
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
@ -633,9 +575,9 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Return this servlet's ThemeSource, if any; else return <code>null</code>.
* <p>Default is to return the WebApplicationContext as ThemeSource,
* provided that it implements the ThemeSource interface.
* Return this servlet's ThemeSource, if any; else return <code>null</code>. <p>Default is to return the
* WebApplicationContext as ThemeSource, provided that it implements the ThemeSource interface.
*
* @return the ThemeSource, if any
* @see #getWebApplicationContext()
*/
@ -650,18 +592,18 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* Obtain this servlet's MultipartResolver, if any.
* @return the MultipartResolver used by this servlet, or <code>null</code>
* if none (indicating that no multipart support is available)
*
* @return the MultipartResolver used by this servlet, or <code>null</code> if none (indicating that no multipart
* support is available)
*/
public final MultipartResolver getMultipartResolver() {
return this.multipartResolver;
}
/**
* Return the default strategy object for the given strategy interface.
* <p>The default implementation delegates to {@link #getDefaultStrategies},
* expecting a single object in the list.
* Return the default strategy object for the given strategy interface. <p>The default implementation delegates to
* {@link #getDefaultStrategies}, expecting a single object in the list.
*
* @param context the current WebApplicationContext
* @param strategyInterface the strategy interface
* @return the corresponding strategy object
@ -677,10 +619,10 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Create a List of default strategy objects for the given strategy interface.
* <p>The default implementation uses the "DispatcherServlet.properties" file
* (in the same package as the DispatcherServlet class) to determine the class names.
* It instantiates the strategy objects through the context's BeanFactory.
* Create a List of default strategy objects for the given strategy interface. <p>The default implementation uses the
* "DispatcherServlet.properties" file (in the same package as the DispatcherServlet class) to determine the class
* names. It instantiates the strategy objects through the context's BeanFactory.
*
* @param context the current WebApplicationContext
* @param strategyInterface the strategy interface
* @return the List of corresponding strategy objects
@ -717,13 +659,12 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Create a default strategy.
* <p>The default implementation uses
* {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
* Create a default strategy. <p>The default implementation uses {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
*
* @param context the current WebApplicationContext
* @param clazz the strategy implementation class to instantiate
* @throws BeansException if initialization failed
* @return the fully configured strategy instance
* @throws BeansException if initialization failed
* @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
*/
@ -731,17 +672,16 @@ public class DispatcherServlet extends FrameworkServlet {
return context.getAutowireCapableBeanFactory().createBean(clazz);
}
/**
* Exposes the DispatcherServlet-specific request attributes and
* delegates to {@link #doDispatch} for the actual dispatching.
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch} for the actual
* dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() +
"' processing " + request.getMethod() + " request for [" + requestUri + "]");
logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
" request for [" + requestUri + "]");
}
// Keep a snapshot of the request attributes in case of an include,
@ -777,12 +717,11 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed
* HandlerAdapters to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or
* handlers themselves to decide which methods are acceptable.
* Process the actual dispatching to the handler. <p>The handler will be obtained by applying the servlet's
* HandlerMappings in order. The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters to
* find the first that supports the handler class. <p>All HTTP methods are handled by this method. It's up to
* HandlerAdapters or handlers themselves to decide which methods are acceptable.
*
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
@ -855,8 +794,8 @@ public class DispatcherServlet extends FrameworkServlet {
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
getServletName() + "': assuming HandlerAdapter completed request handling");
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
@ -885,15 +824,16 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Override HttpServlet's <code>getLastModified</code> method to evaluate
* the Last-Modified value of the mapped handler.
* Override HttpServlet's <code>getLastModified</code> method to evaluate the Last-Modified value of the mapped
* handler.
*/
@Override
protected long getLastModified(HttpServletRequest request) {
if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() +
"' determining Last-Modified value for [" + requestUri + "]");
logger.debug(
"DispatcherServlet with name '" + getServletName() + "' determining Last-Modified value for [" +
requestUri + "]");
}
try {
HandlerExecutionChain mappedHandler = getHandler(request, true);
@ -918,12 +858,11 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
/**
* Build a LocaleContext for the given request, exposing the request's
* primary locale as current locale.
* <p>The default implementation uses the dispatcher's LocaleResolver
* to obtain the current locale, which might change during a request.
* Build a LocaleContext for the given request, exposing the request's primary locale as current locale. <p>The default
* implementation uses the dispatcher's LocaleResolver to obtain the current locale, which might change during a
* request.
*
* @param request current HTTP request
* @return the corresponding LocaleContext
*/
@ -933,6 +872,7 @@ public class DispatcherServlet extends FrameworkServlet {
public Locale getLocale() {
return localeResolver.resolveLocale(request);
}
@Override
public String toString() {
return getLocale().toString();
@ -941,8 +881,9 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Convert the request into a multipart request, and make multipart resolver available.
* If no multipart resolver is set, simply use the existing request.
* Convert the request into a multipart request, and make multipart resolver available. If no multipart resolver is
* set, simply use the existing request.
*
* @param request current HTTP request
* @return the processed request (multipart wrapper if necessary)
* @see MultipartResolver#resolveMultipart
@ -963,6 +904,7 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* Clean up any resources used by the given multipart request (if any).
*
* @param request current HTTP request
* @see MultipartResolver#cleanupMultipart
*/
@ -973,15 +915,14 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Return the HandlerExecutionChain for this request.
* Try all handler mappings in order.
* Return the HandlerExecutionChain for this request. Try all handler mappings in order.
*
* @param request current HTTP request
* @param cache whether to cache the HandlerExecutionChain in a request attribute
* @return the HandlerExceutionChain, or <code>null</code> if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
HandlerExecutionChain handler =
(HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
HandlerExecutionChain handler = (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
if (handler != null) {
if (!cache) {
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
@ -1007,6 +948,7 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* No handler found -> set appropriate HTTP response status.
*
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception if preparing the response failed
@ -1014,17 +956,17 @@ public class DispatcherServlet extends FrameworkServlet {
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request);
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" +
requestUri + "] in DispatcherServlet with name '" + getServletName() + "'");
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
/**
* Return the HandlerAdapter for this handler object.
*
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler.
* This is a fatal error.
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
@ -1041,17 +983,19 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* Determine an error ModelAndView via the registered HandlerExceptionResolvers.
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of
* the exception (for example, if multipart resolution failed)
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to
* @throws Exception if no error ModelAndView found
*/
protected ModelAndView processHandlerException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
protected ModelAndView processHandlerException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// Check registerer HandlerExceptionResolvers...
ModelAndView exMv = null;
@ -1066,35 +1010,26 @@ public class DispatcherServlet extends FrameworkServlet {
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv,
ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
// Send default responses for well-known exceptions, if possible.
if (ex instanceof HttpRequestMethodNotSupportedException && !response.isCommitted()) {
String[] supportedMethods = ((HttpRequestMethodNotSupportedException) ex).getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return null;
}
throw ex;
}
/**
* Render the given ModelAndView. This is the last stage in handling a request.
* It may involve resolving the view by name.
* Render the given ModelAndView. This is the last stage in handling a request. It may involve resolving the view by
* name.
*
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
throws Exception {
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
@ -1106,8 +1041,9 @@ public class DispatcherServlet extends FrameworkServlet {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
throw new ServletException(
"Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +
getServletName() + "'");
}
}
else {
@ -1128,6 +1064,7 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* Translate the supplied request into a default view name.
*
* @param request current HTTP servlet request
* @return the view name (or <code>null</code> if no default found)
* @throws Exception if view name translation failed
@ -1137,22 +1074,22 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Resolve the given view name into a View object (to be rendered).
* <p>Default implementations asks all ViewResolvers of this dispatcher.
* Can be overridden for custom resolution strategies, potentially based
* on specific model attributes or request parameters.
* Resolve the given view name into a View object (to be rendered). <p>Default implementations asks all ViewResolvers
* of this dispatcher. Can be overridden for custom resolution strategies, potentially based on specific model
* attributes or request parameters.
*
* @param viewName the name of the view to resolve
* @param model the model to be passed to the view
* @param locale the current locale
* @param request current HTTP servlet request
* @return the View object, or <code>null</code> if none found
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
* @throws Exception if the view cannot be resolved (typically in case of problems creating an actual View object)
* @see ViewResolver#resolveViewName
*/
protected View resolveViewName(
String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request)
throws Exception {
protected View resolveViewName(String viewName,
Map<String, Object> model,
Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
@ -1164,18 +1101,19 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle
* invocation has successfully completed and returned true.
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors. Will just invoke afterCompletion for all
* interceptors whose preHandle invocation has successfully completed and returned true.
*
* @param mappedHandler the mapped HandlerExecutionChain
* @param interceptorIndex index of last interceptor that successfully completed
* @param ex Exception thrown on handler execution, or <code>null</code> if none
* @see HandlerInterceptor#afterCompletion
*/
private void triggerAfterCompletion(
HandlerExecutionChain mappedHandler, int interceptorIndex,
HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,
int interceptorIndex,
HttpServletRequest request,
HttpServletResponse response,
Exception ex) throws Exception {
// Apply afterCompletion methods of registered interceptors.
if (mappedHandler != null) {
@ -1196,9 +1134,9 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* Restore the request attributes after an include.
*
* @param request current HTTP request
* @param attributesSnapshot the snapshot of the request attributes
* before the include
* @param attributesSnapshot the snapshot of the request attributes before the include
*/
private void restoreAttributesAfterInclude(HttpServletRequest request, Map attributesSnapshot) {
logger.debug("Restoring snapshot of request attributes after include");

View File

@ -13,6 +13,8 @@ org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.m
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.handler.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

View File

@ -0,0 +1,190 @@
/*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract base class for {@link HandlerExceptionResolver} implementations. <p>Provides a set of mapped handlers that
* the resolver should map to, and the {@link Ordered} implementation.
*
* @author Arjen Poutsma
* @since 3.0
*/
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Ordered.LOWEST_PRECEDENCE;
private Set mappedHandlers;
private Class[] mappedHandlerClasses;
private Log warnLogger;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/**
* Specify the set of handlers that this exception resolver should apply to. The exception mappings and the default
* error view will only apply to the specified handlers. <p>If no handlers and handler classes are set, the exception
* mappings and the default error view will apply to all handlers. This means that a specified default error view will
* be used as fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be ignored in this
* case.
*/
public void setMappedHandlers(Set mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}
/**
* Specify the set of classes that this exception resolver should apply to. The exception mappings and the default
* error view will only apply to handlers of the specified type; the specified types may be interfaces and superclasses
* of handlers as well. <p>If no handlers and handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used as fallback for all
* exceptions; any further HandlerExceptionResolvers in the chain will be ignored in this case.
*/
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set the log category for warn logging. The name will be passed to the underlying logger implementation through
* Commons Logging, getting interpreted as log category according to the logger's configuration. <p>Default is no warn
* logging. Specify this setting to activate warn logging into a specific category. Alternatively, override the {@link
* #logException} method for custom logging.
*
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see org.apache.log4j.Logger#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = LogFactory.getLog(loggerName);
}
/**
* Checks whether this resolver is supposed to apply (i.e. the handler matches in case of "mappedHandlers" having been
* specified), then delegates to the {@link #doResolveException} template method.
*/
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if (shouldApplyTo(request, handler)) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler. <p>The default implementation checks against
* the specified mapped handlers and handler classes, if any.
*
* @param request current HTTP request
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
/**
* Log the given exception at warn level, provided that warn logging has been activated through the {@link
* #setWarnLogCategory "warnLogCategory"} property. <p>Calls {@link #buildLogMessage} in order to determine the
* concrete message to log. Always passes the full exception to the logger.
*
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, HttpServletRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request), ex);
}
}
/**
* Build a log message for the given exception, occured during processing the given request.
*
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Handler execution resulted in exception";
}
/**
* Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that
* represents a specific error page if appropriate. <p>May be overridden in subclasses, in order to apply specific
* exception checks. Note that this template method will be invoked <i>after</i> checking whether this resolved applies
* ("mappedHandlers" etc), so an implementation may simply proceed with its actual exception handling.
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected abstract ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);
}

View File

@ -0,0 +1,280 @@
/*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/**
* Default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver
* HandlerExceptionResolver} interface that resolves standard Spring exceptions. <p>Default implementations typically
* set the response status.
*
* @author Arjen Poutsma
* @see #handleNoSuchRequestHandlingMethod
* @see #handleHttpRequestMethodNotSupported
* @see #handleHttpMediaTypeNotSupported
* @see #handleMissingServletRequestParameter
* @see #handleTypeMismatch
* @see #handleHttpMessageNotReadable
* @see #handleHttpMessageNotWritable
* @since 3.0
*/
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* Log category to use when no mapped handler is found for a request.
*
* @see #pageNotFoundLogger
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Additional logger to use when no mapped handler is found for a request.
*
* @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
/** Sets the {@linkplain #setOrder(int) order} to {@link #LOWEST_PRECEDENCE}. */
public DefaultHandlerExceptionResolver() {
setOrder(Ordered.LOWEST_PRECEDENCE);
}
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
/**
* Handle the case where no request handler method was found. <p>The default implementation logs a warning, sends an
* HTTP 404 error, and returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, or the
* NoSuchRequestHandlingMethodException could be rethrown as-is.
*
* @param ex the NoSuchRequestHandlingMethodException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return new ModelAndView();
}
/**
* Handle the case where no request handler method was found for the particular HTTP request method. <p>The default
* implementation logs a warning, sends an HTTP 405 error, sets the "Allow" header, and returns an empty {@code
* ModelAndView}. Alternatively, a fallback view could be chosen, or the HttpRequestMethodNotSupportedException could
* be rethrown as-is.
*
* @param ex the HttpRequestMethodNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
String[] supportedMethods = ex.getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return new ModelAndView();
}
/**
* Handle the case where no {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}
* were found for the PUT or POSTed content. <p>The default implementation sends an HTTP 415 error, sets the "Allow"
* header, and returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, or the
* HttpMediaTypeNotSupportedException could be rethrown as-is.
*
* @param ex the HttpMediaTypeNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
if (mediaTypes != null) {
response.setHeader("Accept", MediaType.toString(mediaTypes));
}
return new ModelAndView();
}
/**
* Handle the case when a required parameter is missing. <p>The default implementation sends an HTTP 400 error, and
* returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, or the
* MissingServletRequestParameterException could be rethrown as-is.
*
* @param ex the MissingServletRequestParameterException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView();
}
/**
* Handle the case when a {@link org.springframework.web.bind.WebDataBinder} conversion error occurs. <p>The default
* implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}. Alternatively, a fallback view
* could be chosen, or the TypeMismatchException could be rethrown as-is.
*
* @param ex the TypeMismatchException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleTypeMismatch(TypeMismatchException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView();
}
/**
* Handle the case where a {@linkplain org.springframework.http.converter.HttpMessageConverter message converter} can
* not read from a HTTP request. <p>The default implementation sends an HTTP 400 error, and returns an empty {@code
* ModelAndView}. Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotSupportedException could be
* rethrown as-is.
*
* @param ex the HttpMessageNotReadableException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView();
}
/**
* Handle the case where a {@linkplain org.springframework.http.converter.HttpMessageConverter message converter} can
* not write to a HTTP request. <p>The default implementation sends an HTTP 500 error, and returns an empty {@code
* ModelAndView}. Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotSupportedException could be
* rethrown as-is.
*
* @param ex the HttpMessageNotWritableException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMessageNotWritable(HttpMessageNotWritableException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return new ModelAndView();
}
}

View File

@ -18,51 +18,29 @@ package org.springframework.web.servlet.handler;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
/**
* {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation
* that allows for mapping exception class names to view names, either for a
* set of given handlers or for all handlers in the DispatcherServlet.
* {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation that allows for mapping exception
* class names to view names, either for a set of given handlers or for all handlers in the DispatcherServlet.
*
* <p>Error views are analogous to error page JSPs, but can be used with any
* kind of exception including any checked one, with fine-granular mappings for
* specific handlers.
* <p>Error views are analogous to error page JSPs, but can be used with any kind of exception including any checked
* one, with fine-granular mappings for specific handlers.
*
* @author Juergen Hoeller
* @since 22.11.2003
* @author Arjen Poutsma
* @see org.springframework.web.servlet.DispatcherServlet
* @since 22.11.2003
*/
public class SimpleMappingExceptionResolver implements HandlerExceptionResolver, Ordered {
public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* The default name of the exception attribute: "exception".
*/
/** The default name of the exception attribute: "exception". */
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Set mappedHandlers;
private Class[] mappedHandlerClasses;
private Log warnLogger;
private Properties exceptionMappings;
private String defaultErrorView;
@ -71,74 +49,18 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/**
* Specify the set of handlers that this exception resolver should apply to.
* The exception mappings and the default error view will only apply
* to the specified handlers.
* <p>If no handlers and handler classes are set, the exception mappings
* and the default error view will apply to all handlers. This means that
* a specified default error view will be used as fallback for all exceptions;
* any further HandlerExceptionResolvers in the chain will be ignored in
* this case.
*/
public void setMappedHandlers(Set mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}
/**
* Specify the set of classes that this exception resolver should apply to.
* The exception mappings and the default error view will only apply
* to handlers of the specified type; the specified types may be interfaces
* and superclasses of handlers as well.
* <p>If no handlers and handler classes are set, the exception mappings
* and the default error view will apply to all handlers. This means that
* a specified default error view will be used as fallback for all exceptions;
* any further HandlerExceptionResolvers in the chain will be ignored in
* this case.
*/
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set the log category for warn logging. The name will be passed to the
* underlying logger implementation through Commons Logging, getting
* interpreted as log category according to the logger's configuration.
* <p>Default is no warn logging. Specify this setting to activate
* warn logging into a specific category. Alternatively, override
* the {@link #logException} method for custom logging.
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see org.apache.log4j.Logger#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = LogFactory.getLog(loggerName);
}
/**
* Set the mappings between exception class names and error view names.
* The exception class name can be a substring, with no wildcard support
* at present. A value of "ServletException" would match
* <code>javax.servlet.ServletException</code> and subclasses, for example.
* <p><b>NB:</b> Consider carefully how specific the pattern is, and whether
* to include package information (which isn't mandatory). For example,
* "Exception" will match nearly anything, and will probably hide other rules.
* "java.lang.Exception" would be correct if "Exception" was meant to define
* a rule for all checked exceptions. With more unusual exception names such
* as "BaseBusinessException" there's no need to use a FQN.
* <p>Follows the same matching algorithm as RuleBasedTransactionAttribute
* and RollbackRuleAttribute.
* @param mappings exception patterns (can also be fully qualified class names)
* as keys, and error view names as values
* Set the mappings between exception class names and error view names. The exception class name can be a substring,
* with no wildcard support at present. A value of "ServletException" would match
* <code>javax.servlet.ServletException</code> and subclasses, for example. <p><b>NB:</b> Consider carefully how
* specific the pattern is, and whether to include package information (which isn't mandatory). For example,
* "Exception" will match nearly anything, and will probably hide other rules. "java.lang.Exception" would be correct
* if "Exception" was meant to define a rule for all checked exceptions. With more unusual exception names such as
* "BaseBusinessException" there's no need to use a FQN. <p>Follows the same matching algorithm as
* RuleBasedTransactionAttribute and RollbackRuleAttribute.
*
* @param mappings exception patterns (can also be fully qualified class names) as keys, and error view names as
* values
* @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute
*/
@ -147,24 +69,20 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
}
/**
* Set the name of the default error view.
* This view will be returned if no specific mapping was found.
* <p>Default is none.
* Set the name of the default error view. This view will be returned if no specific mapping was found. <p>Default is
* none.
*/
public void setDefaultErrorView(String defaultErrorView) {
this.defaultErrorView = defaultErrorView;
}
/**
* Set the default HTTP status code that this exception resolver will apply
* if it resolves an error view.
* <p>Note that this error code will only get applied in case of a top-level
* request. It will not be set for an include request, since the HTTP status
* cannot be modified from within an include.
* <p>If not specified, no status code will be applied, either leaving this to
* the controller or view, or keeping the servlet engine's default of 200 (OK).
* @param defaultStatusCode HTTP status code value, for example
* 500 (SC_INTERNAL_SERVER_ERROR) or 404 (SC_NOT_FOUND)
* Set the default HTTP status code that this exception resolver will apply if it resolves an error view. <p>Note that
* this error code will only get applied in case of a top-level request. It will not be set for an include request,
* since the HTTP status cannot be modified from within an include. <p>If not specified, no status code will be
* applied, either leaving this to the controller or view, or keeping the servlet engine's default of 200 (OK).
*
* @param defaultStatusCode HTTP status code value, for example 500 (SC_INTERNAL_SERVER_ERROR) or 404 (SC_NOT_FOUND)
* @see javax.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR
* @see javax.servlet.http.HttpServletResponse#SC_NOT_FOUND
*/
@ -173,84 +91,33 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
}
/**
* Set the name of the model attribute as which the exception should
* be exposed. Default is "exception".
* <p>This can be either set to a different attribute name or to
* <code>null</code> for not exposing an exception attribute at all.
* Set the name of the model attribute as which the exception should be exposed. Default is "exception". <p>This can be
* either set to a different attribute name or to <code>null</code> for not exposing an exception attribute at all.
*
* @see #DEFAULT_EXCEPTION_ATTRIBUTE
*/
public void setExceptionAttribute(String exceptionAttribute) {
this.exceptionAttribute = exceptionAttribute;
}
/**
* Checks whether this resolver is supposed to apply (i.e. the handler
* matches in case of "mappedHandlers" having been specified), then
* delegates to the {@link #doResolveException} template method.
*/
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler.
* <p>The default implementation checks against the specified mapped handlers
* and handler classes, if any.
* @param request current HTTP request
* @param handler the executed handler, or <code>null</code> if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception
* for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
/**
* Actually resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* <p>May be overridden in subclasses, in order to apply specific exception checks.
* Note that this template method will be invoked <i>after</i> checking whether
* this resolved applies ("mappedHandlers" etc), so an implementation may simply
* proceed with its actual exception handling.
* Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that
* represents a specific error page if appropriate. <p>May be overridden in subclasses, in order to apply specific
* exception checks. Note that this template method will be invoked <i>after</i> checking whether this resolved applies
* ("mappedHandlers" etc), so an implementation may simply proceed with its actual exception handling.
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// Expose ModelAndView for chosen error view.
String viewName = determineViewName(ex, request);
@ -268,40 +135,10 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
}
}
/**
* Log the given exception at warn level, provided that warn logging has been
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
* <p>Calls {@link #buildLogMessage} in order to determine the concrete message
* to log. Always passes the full exception to the logger.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, HttpServletRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request), ex);
}
}
/**
* Build a log message for the given exception, occured during processing
* the given request.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Handler execution resulted in exception";
}
/**
* Determine the view name for the given exception, searching the
* {@link #setExceptionMappings "exceptionMappings"}, using the
* {@link #setDefaultErrorView "defaultErrorView"} as fallback.
* Determine the view name for the given exception, searching the {@link #setExceptionMappings "exceptionMappings"},
* using the {@link #setDefaultErrorView "defaultErrorView"} as fallback.
*
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the resolved view name, or <code>null</code> if none found
@ -315,8 +152,8 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
// Return default error view else, if defined.
if (viewName == null && this.defaultErrorView != null) {
if (logger.isDebugEnabled()) {
logger.debug("Resolving to default view '" + this.defaultErrorView +
"' for exception of type [" + ex.getClass().getName() + "]");
logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type [" +
ex.getClass().getName() + "]");
}
viewName = this.defaultErrorView;
}
@ -325,6 +162,7 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
/**
* Find a matching view name in the given exception mappings.
*
* @param exceptionMappings mappings between exception class names and error view names
* @param ex the exception that got thrown during handler execution
* @return the view name, or <code>null</code> if none found
@ -351,11 +189,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
}
/**
* Return the depth to the superclass matching.
* <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
* <p>Follows the same algorithm as
* {@link org.springframework.transaction.interceptor.RollbackRuleAttribute}.
* Return the depth to the superclass matching. <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins. <p>Follows the same algorithm as {@link
* org.springframework.transaction.interceptor.RollbackRuleAttribute}.
*/
protected int getDepth(String exceptionMapping, Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0);
@ -373,17 +209,15 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Determine the HTTP status code to apply for the given error view.
* <p>The default implementation always returns the specified
* {@link #setDefaultStatusCode "defaultStatusCode"}, as a common
* status code for all error views. Override this in a custom subclass
* to determine a specific status code for the given view.
* Determine the HTTP status code to apply for the given error view. <p>The default implementation always returns the
* specified {@link #setDefaultStatusCode "defaultStatusCode"}, as a common status code for all error views. Override
* this in a custom subclass to determine a specific status code for the given view.
*
* @param request current HTTP request
* @param viewName the name of the error view
* @return the HTTP status code to use, or <code>null</code> for the
* servlet container's default (200 in case of a standard error view)
* @return the HTTP status code to use, or <code>null</code> for the servlet container's default (200 in case of a
* standard error view)
* @see #setDefaultStatusCode
* @see #applyStatusCodeIfPossible
*/
@ -392,8 +226,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
}
/**
* Apply the specified HTTP status code to the given response, if possible
* (that is, if not executing within an include request).
* Apply the specified HTTP status code to the given response, if possible (that is, if not executing within an include
* request).
*
* @param request current HTTP request
* @param response current HTTP response
* @param statusCode the status code to apply
@ -412,8 +247,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
}
/**
* Return a ModelAndView for the given request, view name and exception.
* <p>The default implementation delegates to {@link #getModelAndView(String, Exception)}.
* Return a ModelAndView for the given request, view name and exception. <p>The default implementation delegates to
* {@link #getModelAndView(String, Exception)}.
*
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
@ -424,9 +260,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
}
/**
* Return a ModelAndView for the given view name and exception.
* <p>The default implementation adds the specified exception attribute.
* Can be overridden in subclasses.
* Return a ModelAndView for the given view name and exception. <p>The default implementation adds the specified
* exception attribute. Can be overridden in subclasses.
*
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @return the ModelAndView instance

View File

@ -52,7 +52,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
@ -68,7 +67,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.MissingServletRequestParameterException;
@ -101,38 +99,36 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerAdapter}
* interface that maps handler methods based on HTTP paths, HTTP methods and
* request parameters expressed through the {@link RequestMapping} annotation.
* Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} interface that maps handler methods
* based on HTTP paths, HTTP methods and request parameters expressed through the {@link RequestMapping} annotation.
*
* <p>Supports request parameter binding through the {@link RequestParam} annotation.
* Also supports the {@link ModelAttribute} annotation for exposing model attribute
* values to the view, as well as {@link InitBinder} for binder initialization methods
* and {@link SessionAttributes} for automatic session management of specific attributes.
* <p>Supports request parameter binding through the {@link RequestParam} annotation. Also supports the {@link
* ModelAttribute} annotation for exposing model attribute values to the view, as well as {@link InitBinder} for binder
* initialization methods and {@link SessionAttributes} for automatic session management of specific attributes.
*
* <p>This adapter can be customized through various bean properties.
* A common use case is to apply shared binder initialization logic through
* a custom {@link #setWebBindingInitializer WebBindingInitializer}.
* <p>This adapter can be customized through various bean properties. A common use case is to apply shared binder
* initialization logic through a custom {@link #setWebBindingInitializer WebBindingInitializer}.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.5
* @see #setPathMatcher
* @see #setMethodNameResolver
* @see #setWebBindingInitializer
* @see #setSessionAttributeStore
* @since 2.5
*/
public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter {
/**
* Log category to use when no mapped handler is found for a request.
*
* @see #pageNotFoundLogger
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Additional logger to use when no mapped handler is found for a request.
*
* @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
@ -167,12 +163,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
super(false);
}
/**
* Set if URL lookup should always use the full path within the current servlet
* context. Else, the path within the current servlet mapping is used if applicable
* (that is, in the case of a ".../*" servlet mapping in web.xml).
* Set if URL lookup should always use the full path within the current servlet context. Else, the path within the
* current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml).
* <p>Default is "false".
*
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
@ -180,10 +175,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set if context path and request URI should be URL-decoded. Both are returned
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
* <p>Uses either the request encoding or the default encoding according
* to the Servlet spec (ISO-8859-1).
* Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in
* contrast to the servlet path. <p>Uses either the request encoding or the default encoding according to the Servlet
* spec (ISO-8859-1).
*
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
@ -191,10 +186,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and HandlerAdapters.
* Set the UrlPathHelper to use for resolution of lookup paths. <p>Use this to override the default UrlPathHelper with
* a custom subclass, or to share common UrlPathHelper settings across multiple HandlerMappings and HandlerAdapters.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
@ -202,8 +195,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns. Default is AntPathMatcher.
* Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is
* AntPathMatcher.
*
* @see org.springframework.util.AntPathMatcher
*/
public void setPathMatcher(PathMatcher pathMatcher) {
@ -212,9 +206,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the MethodNameResolver to use for resolving default handler methods
* (carrying an empty <code>@RequestMapping</code> annotation).
* <p>Will only kick in when the handler method cannot be resolved uniquely
* Set the MethodNameResolver to use for resolving default handler methods (carrying an empty
* <code>@RequestMapping</code> annotation). <p>Will only kick in when the handler method cannot be resolved uniquely
* through the annotation metadata already.
*/
public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
@ -222,18 +215,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Specify a WebBindingInitializer which will apply pre-configured
* configuration to every DataBinder that this controller uses.
* Specify a WebBindingInitializer which will apply pre-configured configuration to every DataBinder that this
* controller uses.
*/
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Specify the strategy to store session attributes with.
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the HttpSession, using the same
* attribute name as in the model.
* Specify the strategy to store session attributes with. <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the HttpSession, using the same attribute name as in the model.
*/
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null");
@ -241,11 +232,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* 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), this
* setting will apply to <code>@SessionAttributes</code> annotated handlers only.
* 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), this setting will apply to
* <code>@SessionAttributes</code> annotated handlers only.
*
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
*/
@ -254,20 +245,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set if controller execution should be synchronized on the session,
* to serialize parallel invocations from the same client.
* <p>More specifically, the execution of each handler method will get
* synchronized if this flag is "true". The best available session mutex
* will be used for the synchronization; ideally, this will be a mutex
* exposed by HttpSessionMutexListener.
* <p>The session mutex is guaranteed to be the same object during
* the entire lifetime of the session, available under the key defined
* by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
* safe reference to synchronize on for locking on the current session.
* <p>In many cases, the HttpSession reference itself is a safe mutex
* as well, since it will always be the same object reference for the
* same active logical session. However, this is not guaranteed across
* different servlet containers; the only 100% safe way is a session mutex.
* Set if controller execution should be synchronized on the session, to serialize parallel invocations from the same
* client. <p>More specifically, the execution of each handler method will get synchronized if this flag is "true". The
* best available session mutex will be used for the synchronization; ideally, this will be a mutex exposed by
* HttpSessionMutexListener. <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe
* reference to synchronize on for locking on the current session. <p>In many cases, the HttpSession reference itself
* is a safe mutex as well, since it will always be the same object reference for the same active logical session.
* However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
*
* @see org.springframework.web.util.HttpSessionMutexListener
* @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
*/
@ -276,44 +262,38 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the ParameterNameDiscoverer to use for resolving method parameter
* names if needed (e.g. for default attribute names).
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed (e.g. for default attribute
* names). <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* Set a custom ArgumentResolvers to use for special method parameter types.
* Such a custom ArgumentResolver will kick in first, having a chance to
* resolve an argument value before the standard argument handling kicks in.
* Set a custom ArgumentResolvers to use for special method parameter types. Such a custom ArgumentResolver will kick
* in first, having a chance to resolve an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
}
/**
* Set one or more custom ArgumentResolvers to use for special method
* parameter types. Any such custom ArgumentResolver will kick in first,
* having a chance to resolve an argument value before the standard
* argument handling kicks in.
* Set one or more custom ArgumentResolvers to use for special method parameter types. Any such custom ArgumentResolver
* will kick in first, having a chance to resolve an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}
/**
* Set the message body converters to use. These converters are used to convert
* from and to HTTP requests and responses.
* Set the message body converters to use. These converters are used to convert from and to HTTP requests and
* responses.
*/
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters;
}
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
}
@ -345,32 +325,20 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return invokeHandlerMethod(request, response, handler);
}
protected ModelAndView invokeHandlerMethod(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ExtendedModelMap implicitModel = new BindingAwareModelMap();
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ExtendedModelMap implicitModel = new BindingAwareModelMap();
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(
handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
catch (NoSuchRequestHandlingMethodException ex) {
return handleNoSuchRequestHandlingMethod(ex, request, response);
}
catch (HttpRequestMethodNotSupportedException ex) {
return handleHttpRequestMethodNotSupportedException(ex, request, response);
}
catch (HttpMediaTypeNotSupportedException ex) {
return handleHttpMediaTypeNotSupportedException(ex, request, response);
}
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
public long getLastModified(HttpServletRequest request, Object handler) {
@ -378,89 +346,25 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Handle the case where no request handler method was found.
* <p>The default implementation logs a warning and sends an HTTP 404 error.
* Alternatively, a fallback view could be chosen, or the
* NoSuchRequestHandlingMethodException could be rethrown as-is.
* @param ex the NoSuchRequestHandlingMethodException to be handled
* Template method for creating a new ServletRequestDataBinder instance. <p>The default implementation creates a
* standard ServletRequestDataBinder. This can be overridden for custom ServletRequestDataBinder subclasses.
*
* @param request current HTTP request
* @param response current HTTP response
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleNoSuchRequestHandlingMethod(
NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response)
throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
/**
* Handle the case where no request handler method was found for the particular HTTP request method.
* <p>The default implementation logs a warning, sends an HTTP 405 error and sets the "Allow" header.
* Alternatively, a fallback view could be chosen, or the HttpRequestMethodNotSupportedException
* could be rethrown as-is.
* @param ex the HttpRequestMethodNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException ex, HttpServletRequest request, HttpServletResponse response)
throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
response.addHeader("Allow", StringUtils.arrayToDelimitedString(ex.getSupportedMethods(), ", "));
return null;
}
/**
* Handle the case where no {@linkplain HttpMessageConverter message converters} was found for the PUT or POSTed
* content.
* <p>The default implementation logs a warning, sends an HTTP 415 error and sets the "Allow" header.
* Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotSupportedException
* could be rethrown as-is.
* @param ex the HttpMediaTypeNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMediaTypeNotSupportedException(
HttpMediaTypeNotSupportedException ex, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
response.addHeader("Accept", MediaType.toString(ex.getSupportedMediaTypes()));
return null;
}
/**
* Template method for creating a new ServletRequestDataBinder instance.
* <p>The default implementation creates a standard ServletRequestDataBinder.
* This can be overridden for custom ServletRequestDataBinder subclasses.
* @param request current HTTP request
* @param target the target object to bind onto (or <code>null</code>
* if the binder is just used to convert a plain parameter value)
* @param target the target object to bind onto (or <code>null</code> if the binder is just used to convert a plain
* parameter value)
* @param objectName the objectName of the target object
* @return the ServletRequestDataBinder instance to use
* @throws Exception in case of invalid state or arguments
* @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest)
* @see ServletRequestDataBinder#convertIfNecessary(Object, Class, MethodParameter)
*/
protected ServletRequestDataBinder createBinder(
HttpServletRequest request, Object target, String objectName) throws Exception {
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName)
throws Exception {
return new ServletRequestDataBinder(target, objectName);
}
/**
* Build a HandlerMethodResolver for the given handler type.
*/
/** Build a HandlerMethodResolver for the given handler type. */
private ServletHandlerMethodResolver getMethodResolver(Object handler) {
Class handlerClass = ClassUtils.getUserClass(handler);
ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
@ -471,10 +375,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return resolver;
}
/**
* Servlet-specific subclass of {@link HandlerMethodResolver}.
*/
/** Servlet-specific subclass of {@link HandlerMethodResolver}. */
private class ServletHandlerMethodResolver extends HandlerMethodResolver {
private ServletHandlerMethodResolver(Class<?> handlerType) {
@ -499,7 +400,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
boolean match = false;
if (mappingInfo.paths.length > 0) {
List<String> matchedPaths = new ArrayList<String>(mappingInfo.paths.length);
List<String> matchedPaths = new ArrayList<String>(mappingInfo.paths.length);
for (String mappedPath : mappingInfo.paths) {
if (isPathMatch(mappedPath, lookupPath)) {
if (checkParameters(mappingInfo, request)) {
@ -515,7 +416,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
Collections.sort(matchedPaths, pathComparator);
mappingInfo.matchedPaths = matchedPaths.toArray(new String[matchedPaths.size()]);
mappingInfo.matchedPaths = matchedPaths.toArray(new String[matchedPaths.size()]);
}
else {
// No paths specified: parameter match sufficient.
@ -548,17 +449,19 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
if (oldMappedMethod != null) {
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
lookupPath + "': {" + oldMappedMethod + ", " + handlerMethod +
"}. If you intend to handle the same path in multiple methods, then factor " +
"them out into a dedicated handler class with that path mapped at the type level!");
throw new IllegalStateException(
"Ambiguous handler methods mapped for HTTP path '" + lookupPath + "': {" +
oldMappedMethod + ", " + handlerMethod +
"}. If you intend to handle the same path in multiple methods, then factor " +
"them out into a dedicated handler class with that path mapped at the type level!");
}
}
}
}
if (!targetHandlerMethods.isEmpty()) {
List<RequestMappingInfo> matches = new ArrayList<RequestMappingInfo>(targetHandlerMethods.keySet());
RequestMappingInfoComparator requestMappingInfoComparator = new RequestMappingInfoComparator(pathComparator);
RequestMappingInfoComparator requestMappingInfoComparator =
new RequestMappingInfoComparator(pathComparator);
Collections.sort(matches, requestMappingInfoComparator);
RequestMappingInfo bestMappingMatch = matches.get(0);
if (bestMappingMatch.matchedPaths.length > 0) {
@ -597,7 +500,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
@SuppressWarnings("unchecked")
private void extractHandlerMethodUriTemplates(String mappedPath, String lookupPath, HttpServletRequest request) {
private void extractHandlerMethodUriTemplates(String mappedPath,
String lookupPath,
HttpServletRequest request) {
Map<String, String> variables = null;
boolean hasSuffix = (mappedPath.indexOf('.') != -1);
if (!hasSuffix && pathMatcher.match(mappedPath + ".*", lookupPath)) {
@ -610,7 +515,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
String realPath = "/**/" + mappedPath;
if (pathMatcher.match(realPath, lookupPath)) {
variables = pathMatcher.extractUriTemplateVariables(realPath, lookupPath);
} else {
}
else {
realPath = realPath + ".*";
if (pathMatcher.match(realPath, lookupPath)) {
variables = pathMatcher.extractUriTemplateVariables(realPath, lookupPath);
@ -628,17 +534,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
/**
* Servlet-specific subclass of {@link HandlerMethodInvoker}.
*/
/** Servlet-specific subclass of {@link HandlerMethodInvoker}. */
private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
private boolean responseArgumentUsed = false;
private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore,
parameterNameDiscoverer, customArgumentResolvers, messageConverters);
super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
customArgumentResolvers, messageConverters);
}
@Override
@ -655,8 +558,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
throws Exception {
return AnnotationMethodHandlerAdapter.this.createBinder(
(HttpServletRequest) webRequest.getNativeRequest(), target, objectName);
return AnnotationMethodHandlerAdapter.this
.createBinder((HttpServletRequest) webRequest.getNativeRequest(), target, objectName);
}
@Override
@ -699,14 +602,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
Map<String, String> uriTemplateVariables =
(Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) {
throw new IllegalStateException("Could not find @PathVariable [" + pathVarName + "] in @RequestMapping");
throw new IllegalStateException(
"Could not find @PathVariable [" + pathVarName + "] in @RequestMapping");
}
return uriTemplateVariables.get(pathVarName);
}
@Override
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest)
throws Exception {
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();
@ -745,8 +648,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
@SuppressWarnings("unchecked")
public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,
ExtendedModelMap implicitModel, ServletWebRequest webRequest) {
public ModelAndView getModelAndView(Method handlerMethod,
Class handlerType,
Object returnValue,
ExtendedModelMap implicitModel,
ServletWebRequest webRequest) {
if (returnValue instanceof ModelAndView) {
ModelAndView mav = (ModelAndView) returnValue;
@ -792,7 +698,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
static class RequestMappingInfo {
String[] paths = new String[0];
@ -806,7 +711,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
String bestMatchedPath() {
return matchedPaths.length > 0 ? matchedPaths[0] : null;
}
@Override
public boolean equals(Object obj) {
RequestMappingInfo other = (RequestMappingInfo) obj;
@ -823,16 +728,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Comparator capable of sorting {@link RequestMappingInfo}s (RHIs) so that sorting a list with this comparator will
* result in:
* <ul>
* <li>RHIs with {@linkplain RequestMappingInfo#matchedPaths better matched paths} take prescedence over those with
* a weaker match (as expressed by the {@linkplain PathMatcher#getPatternComparator(String) path pattern
* comparator}.) Typically, this means that patterns without wild chards and uri templates will be ordered before those without.</li>
* <li>RHIs with one single {@linkplain RequestMappingInfo#methods request method} will be ordered before those
* without a method, or with more than one method.</li>
* <li>RHIs with more {@linkplain RequestMappingInfo#params request parameters} will be ordered before those with
* less parameters</li>
* </ol>
* result in: <ul> <li>RHIs with {@linkplain RequestMappingInfo#matchedPaths better matched paths} take prescedence
* over those with a weaker match (as expressed by the {@linkplain PathMatcher#getPatternComparator(String) path
* pattern comparator}.) Typically, this means that patterns without wild chards and uri templates will be ordered
* before those without.</li> <li>RHIs with one single {@linkplain RequestMappingInfo#methods request method} will be
* ordered before those without a method, or with more than one method.</li> <li>RHIs with more {@linkplain
* RequestMappingInfo#params request parameters} will be ordered before those with less parameters</li> </ol>
*/
static class RequestMappingInfoComparator implements Comparator<RequestMappingInfo> {
@ -867,5 +768,5 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return (info1ParamCount < info2ParamCount ? 1 : (info1ParamCount == info2ParamCount ? 0 : -1));
}
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Collections;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.MediaType;
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.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/** @author Arjen Poutsma */
public class DefaultHandlerExceptionResolverTests {
private DefaultHandlerExceptionResolver exceptionResolver;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setUp() {
exceptionResolver = new DefaultHandlerExceptionResolver();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
request.setMethod("GET");
}
@Test
public void handleNoSuchRequestHandlingMethod() {
NoSuchRequestHandlingMethodException ex = new NoSuchRequestHandlingMethodException(request);
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 404, response.getStatus());
}
@Test
public void handleHttpRequestMethodNotSupported() {
HttpRequestMethodNotSupportedException ex =
new HttpRequestMethodNotSupportedException("GET", new String[]{"POST", "PUT"});
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 405, response.getStatus());
assertEquals("Invalid Allow header", "POST, PUT", response.getHeader("Allow"));
}
@Test
public void handleHttpMediaTypeNotSupported() {
HttpMediaTypeNotSupportedException ex = new HttpMediaTypeNotSupportedException(new MediaType("text", "plain"),
Collections.singletonList(new MediaType("application", "pdf")));
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 415, response.getStatus());
assertEquals("Invalid Accept header", "application/pdf", response.getHeader("Accept"));
}
@Test
public void handleMissingServletRequestParameter() {
MissingServletRequestParameterException ex = new MissingServletRequestParameterException("foo", "bar");
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 400, response.getStatus());
}
@Test
public void handleTypeMismatch() {
TypeMismatchException ex = new TypeMismatchException("foo", String.class);
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 400, response.getStatus());
}
@Test
public void handleHttpMessageNotReadable() {
HttpMessageNotReadableException ex = new HttpMessageNotReadableException("foo");
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 400, response.getStatus());
}
@Test
public void handleHttpMessageNotWritable() {
HttpMessageNotWritableException ex = new HttpMessageNotWritableException("foo");
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 500, response.getStatus());
}
}

View File

@ -21,6 +21,7 @@ import java.io.Writer;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
@ -51,6 +52,12 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
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.mock.web.MockServletConfig;
@ -62,7 +69,6 @@ import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.InitBinder;
@ -107,13 +113,25 @@ public class ServletAnnotationControllerTests {
assertEquals("test", response.getContentAsString());
}
@Test(expected = MissingServletRequestParameterException.class)
@Test
public void requiredParamMissing() throws Exception {
initServlet(RequiredParamController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
@Test
public void typeConversionError() throws Exception {
initServlet(RequiredParamController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
request.addParameter("id", "foo");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
@Test
@ -157,7 +175,7 @@ public class ServletAnnotationControllerTests {
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status", HttpServletResponse.SC_METHOD_NOT_ALLOWED, response.getStatus());
String allowHeader = (String)response.getHeader("Allow");
String allowHeader = (String) response.getHeader("Allow");
assertNotNull("No Allow header", allowHeader);
Set<String> allowedMethods = new HashSet<String>();
allowedMethods.addAll(Arrays.asList(StringUtils.delimitedListToStringArray(allowHeader, ", ")));
@ -249,7 +267,6 @@ public class ServletAnnotationControllerTests {
servlet.init(new MockServletConfig());
}
private void doTestAdaptedHandleMethods(final Class<?> controllerClass) throws Exception {
initServlet(controllerClass);
@ -878,6 +895,31 @@ public class ServletAnnotationControllerTests {
assertNotNull("No Accept response header set", response.getHeader("Accept"));
}
@Test
public void badRequestRequestBody() throws ServletException, IOException {
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(RequestBodyController.class));
RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
adapterDef.getPropertyValues().addPropertyValue("messageConverters", new MyMessageConverter());
wac.registerBeanDefinition("handlerAdapter", adapterDef);
wac.refresh();
return wac;
}
};
servlet.init(new MockServletConfig());
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something");
String requestBody = "Hello World";
request.setContent(requestBody.getBytes("UTF-8"));
request.addHeader("Content-Type", "application/pdf");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
/*
* Controllers
*/
@ -893,8 +935,7 @@ public class ServletAnnotationControllerTests {
}
}
/** @noinspection UnusedDeclaration*/
/** @noinspection UnusedDeclaration */
private static class BaseController {
@RequestMapping(method = RequestMethod.GET)
@ -903,7 +944,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
private static class MyAdaptedController {
@ -913,8 +953,10 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping("/myPath2.do")
public void myHandle(@RequestParam("param1") String p1, @RequestParam("param2") int p2,
@RequestHeader("header1") long h1, @CookieValue("cookie1") Cookie c1,
public void myHandle(@RequestParam("param1") String p1,
@RequestParam("param2") int p2,
@RequestHeader("header1") long h1,
@CookieValue("cookie1") Cookie c1,
HttpServletResponse response) throws IOException {
response.getWriter().write("test-" + p1 + "-" + p2 + "-" + h1 + "-" + c1.getValue());
}
@ -930,7 +972,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
@RequestMapping("/*.do")
private static class MyAdaptedController2 {
@ -941,8 +982,11 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping("/myPath2.do")
public void myHandle(@RequestParam("param1") String p1, int param2, HttpServletResponse response,
@RequestHeader("header1") String h1, @CookieValue("cookie1") String c1) throws IOException {
public void myHandle(@RequestParam("param1") String p1,
int param2,
HttpServletResponse response,
@RequestHeader("header1") String h1,
@CookieValue("cookie1") String c1) throws IOException {
response.getWriter().write("test-" + p1 + "-" + param2 + "-" + h1 + "-" + c1);
}
@ -957,13 +1001,15 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
private static class MyAdaptedControllerBase<T> {
@RequestMapping("/myPath2.do")
public void myHandle(@RequestParam("param1") T p1, int param2, @RequestHeader Integer header1,
@CookieValue int cookie1, HttpServletResponse response) throws IOException {
public void myHandle(@RequestParam("param1") T p1,
int param2,
@RequestHeader Integer header1,
@CookieValue int cookie1,
HttpServletResponse response) throws IOException {
response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1);
}
@ -976,7 +1022,6 @@ public class ServletAnnotationControllerTests {
}
}
@RequestMapping("/*.do")
private static class MyAdaptedController3 extends MyAdaptedControllerBase<String> {
@ -986,8 +1031,11 @@ public class ServletAnnotationControllerTests {
}
@Override
public void myHandle(@RequestParam("param1") String p1, int param2, @RequestHeader Integer header1,
@CookieValue int cookie1, HttpServletResponse response) throws IOException {
public void myHandle(@RequestParam("param1") String p1,
int param2,
@RequestHeader Integer header1,
@CookieValue int cookie1,
HttpServletResponse response) throws IOException {
response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1);
}
@ -1012,7 +1060,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
@RequestMapping(method = RequestMethod.GET)
private static class EmptyParameterListHandlerMethodController {
@ -1029,7 +1076,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
public static class MyFormController {
@ -1042,7 +1088,7 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping("/myPath.do")
public String myHandle(@ModelAttribute("myCommand")TestBean tb, BindingResult errors, ModelMap model) {
public String myHandle(@ModelAttribute("myCommand") TestBean tb, BindingResult errors, ModelMap model) {
if (!model.containsKey("myKey")) {
model.addAttribute("myKey", "myValue");
}
@ -1050,7 +1096,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
public static class MyModelFormController {
@ -1063,7 +1108,7 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping("/myPath.do")
public String myHandle(@ModelAttribute("myCommand")TestBean tb, BindingResult errors, Model model) {
public String myHandle(@ModelAttribute("myCommand") TestBean tb, BindingResult errors, Model model) {
if (!model.containsAttribute("myKey")) {
model.addAttribute("myKey", "myValue");
}
@ -1071,13 +1116,13 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
private static class MyCommandProvidingFormController<T, TB, TB2> extends MyFormController {
@SuppressWarnings("unused")
@ModelAttribute("myCommand")
private TestBean createTestBean(@RequestParam T defaultName, Map<String, Object> model,
private TestBean createTestBean(@RequestParam T defaultName,
Map<String, Object> model,
@RequestParam Date date) {
model.put("myKey", "myOriginalValue");
return new TestBean(defaultName.getClass().getSimpleName() + ":" + defaultName.toString());
@ -1085,7 +1130,7 @@ public class ServletAnnotationControllerTests {
@Override
@RequestMapping("/myPath.do")
public String myHandle(@ModelAttribute("myCommand")TestBean tb, BindingResult errors, ModelMap model) {
public String myHandle(@ModelAttribute("myCommand") TestBean tb, BindingResult errors, ModelMap model) {
return super.myHandle(tb, errors, model);
}
@ -1110,21 +1155,18 @@ public class ServletAnnotationControllerTests {
}
}
private static class MySpecialArg {
public MySpecialArg(String value) {
}
}
@Controller
private static class MyTypedCommandProvidingFormController
extends MyCommandProvidingFormController<Integer, TestBean, ITestBean> {
}
@Controller
private static class MyBinderInitializingCommandProvidingFormController extends MyCommandProvidingFormController {
@ -1138,7 +1180,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
private static class MySpecificBinderInitializingCommandProvidingFormController
extends MyCommandProvidingFormController {
@ -1155,7 +1196,6 @@ public class ServletAnnotationControllerTests {
}
}
private static class MyWebBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) {
@ -1166,7 +1206,6 @@ public class ServletAnnotationControllerTests {
}
}
private static class MySpecialArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
@ -1177,7 +1216,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
@RequestMapping("/myPath.do")
private static class MyParameterDispatchingController {
@ -1223,7 +1261,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
@RequestMapping(value = "/myPath.do", params = {"active"})
private static class MyConstrainedParameterDispatchingController {
@ -1239,14 +1276,12 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
@RequestMapping(value = "/*.do", method = RequestMethod.POST, params = "myParam=myValue")
private static class MyPostMethodNameDispatchingController extends MethodNameDispatchingController {
}
@Controller
@RequestMapping("/myApp/*")
private static class MyRelativePathDispatchingController {
@ -1272,7 +1307,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
private static class MyNullCommandController {
@ -1287,8 +1321,11 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping("/myPath")
public void handle(@ModelAttribute TestBean testBean, Errors errors, @ModelAttribute TestPrincipal modelPrinc,
OtherPrincipal requestPrinc, Writer writer) throws IOException {
public void handle(@ModelAttribute TestBean testBean,
Errors errors,
@ModelAttribute TestPrincipal modelPrinc,
OtherPrincipal requestPrinc,
Writer writer) throws IOException {
assertNull(testBean);
assertNotNull(modelPrinc);
assertNotNull(requestPrinc);
@ -1298,7 +1335,6 @@ public class ServletAnnotationControllerTests {
}
}
private static class TestPrincipal implements Principal {
public String getName() {
@ -1306,7 +1342,6 @@ public class ServletAnnotationControllerTests {
}
}
private static class OtherPrincipal implements Principal {
public String getName() {
@ -1314,7 +1349,6 @@ public class ServletAnnotationControllerTests {
}
}
private static class TestViewResolver implements ViewResolver {
public View resolveViewName(final String viewName, Locale locale) throws Exception {
@ -1345,9 +1379,9 @@ public class ServletAnnotationControllerTests {
}
List<TestBean> testBeans = (List<TestBean>) model.get("testBeanList");
if (errors.hasFieldErrors("age")) {
response.getWriter().write(viewName + "-" + tb.getName() + "-" +
errors.getFieldError("age").getCode() + "-" + testBeans.get(0).getName() + "-" +
model.get("myKey"));
response.getWriter()
.write(viewName + "-" + tb.getName() + "-" + errors.getFieldError("age").getCode() +
"-" + testBeans.get(0).getName() + "-" + model.get("myKey"));
}
else {
response.getWriter().write(viewName + "-" + tb.getName() + "-" + tb.getAge() + "-" +
@ -1358,7 +1392,6 @@ public class ServletAnnotationControllerTests {
}
}
public static class ParentController {
@RequestMapping(method = RequestMethod.GET)
@ -1366,7 +1399,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
@RequestMapping("/child/test")
public static class ChildController extends ParentController {
@ -1376,68 +1408,66 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
public static class RequiredParamController {
@RequestMapping("/myPath.do")
public void myHandle(@RequestParam(value = "id", required = true) String id,
public void myHandle(@RequestParam(value = "id", required = true) int id,
@RequestHeader(value = "header", required = true) String header) {
}
}
@Controller
public static class OptionalParamController {
@RequestMapping("/myPath.do")
public void myHandle(@RequestParam(required = false) String id, @RequestParam(required = false) boolean flag,
@RequestHeader(value = "header", required = false) String header, HttpServletResponse response)
throws IOException {
public void myHandle(@RequestParam(required = false) String id,
@RequestParam(required = false) boolean flag,
@RequestHeader(value = "header", required = false) String header,
HttpServletResponse response) throws IOException {
response.getWriter().write(String.valueOf(id) + "-" + flag + "-" + String.valueOf(header));
}
}
@Controller
public static class DefaultValueParamController {
@RequestMapping("/myPath.do")
public void myHandle(@RequestParam(value = "id", defaultValue = "foo") String id,
@RequestHeader(defaultValue = "bar") String header, HttpServletResponse response)
throws IOException {
@RequestHeader(defaultValue = "bar") String header,
HttpServletResponse response) throws IOException {
response.getWriter().write(String.valueOf(id) + "-" + String.valueOf(header));
}
}
@Controller
public static class MethodNotAllowedController {
@RequestMapping(value="/myPath.do", method = RequestMethod.DELETE)
@RequestMapping(value = "/myPath.do", method = RequestMethod.DELETE)
public void delete() {
}
@RequestMapping(value="/myPath.do", method = RequestMethod.HEAD)
@RequestMapping(value = "/myPath.do", method = RequestMethod.HEAD)
public void head() {
}
@RequestMapping(value="/myPath.do", method = RequestMethod.OPTIONS)
@RequestMapping(value = "/myPath.do", method = RequestMethod.OPTIONS)
public void options() {
}
@RequestMapping(value="/myPath.do", method = RequestMethod.POST)
@RequestMapping(value = "/myPath.do", method = RequestMethod.POST)
public void post() {
}
@RequestMapping(value="/myPath.do", method = RequestMethod.PUT)
@RequestMapping(value = "/myPath.do", method = RequestMethod.PUT)
public void put() {
}
@RequestMapping(value="/myPath.do", method = RequestMethod.TRACE)
@RequestMapping(value = "/myPath.do", method = RequestMethod.TRACE)
public void trace() {
}
@RequestMapping(value="/otherPath.do", method = RequestMethod.GET)
@RequestMapping(value = "/otherPath.do", method = RequestMethod.GET)
public void get() {
}
}
@ -1445,7 +1475,6 @@ public class ServletAnnotationControllerTests {
@Controller
public static class PathOrderingController {
@RequestMapping(value = {"/dir/myPath1.do", "/**/*.do"})
public void method1(Writer writer) throws IOException {
writer.write("method1");
@ -1466,4 +1495,26 @@ public class ServletAnnotationControllerTests {
}
}
public static class MyMessageConverter implements HttpMessageConverter {
public boolean supports(Class clazz) {
return true;
}
public List getSupportedMediaTypes() {
return Collections.singletonList(new MediaType("application", "pdf"));
}
public Object read(Class clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new HttpMessageNotReadableException("Could not read");
}
public void write(Object o, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
throw new UnsupportedOperationException("Not implemented");
}
}
}

View File

@ -5,6 +5,7 @@ import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.*;
import org.junit.Test;
@ -24,9 +25,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
* @author Arjen Poutsma
*/
/** @author Arjen Poutsma */
public class UriTemplateServletAnnotationControllerTests {
private DispatcherServlet servlet;
@ -92,6 +91,16 @@ public class UriTemplateServletAnnotationControllerTests {
}
@Test
public void typeConversionError() throws Exception {
initServlet(SimpleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.xml");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
private void initServlet(final Class<?> controllerclass) throws ServletException {
servlet = new DispatcherServlet() {
@Override