0001: /*
0002: * Copyright 2002-2007 the original author or authors.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.springframework.web.servlet;
0018:
0019: import java.io.IOException;
0020: import java.util.ArrayList;
0021: import java.util.Collections;
0022: import java.util.Enumeration;
0023: import java.util.HashMap;
0024: import java.util.HashSet;
0025: import java.util.Iterator;
0026: import java.util.List;
0027: import java.util.Locale;
0028: import java.util.Map;
0029: import java.util.Properties;
0030: import java.util.Set;
0031:
0032: import javax.servlet.ServletException;
0033: import javax.servlet.http.HttpServletRequest;
0034: import javax.servlet.http.HttpServletResponse;
0035:
0036: import org.apache.commons.logging.Log;
0037: import org.apache.commons.logging.LogFactory;
0038:
0039: import org.springframework.beans.BeansException;
0040: import org.springframework.beans.factory.BeanFactoryUtils;
0041: import org.springframework.beans.factory.BeanInitializationException;
0042: import org.springframework.beans.factory.NoSuchBeanDefinitionException;
0043: import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
0044: import org.springframework.context.ApplicationContext;
0045: import org.springframework.context.i18n.LocaleContext;
0046: import org.springframework.context.i18n.LocaleContextHolder;
0047: import org.springframework.core.OrderComparator;
0048: import org.springframework.core.io.ClassPathResource;
0049: import org.springframework.core.io.support.PropertiesLoaderUtils;
0050: import org.springframework.ui.context.ThemeSource;
0051: import org.springframework.util.ClassUtils;
0052: import org.springframework.util.StringUtils;
0053: import org.springframework.web.context.request.RequestAttributes;
0054: import org.springframework.web.context.request.RequestContextHolder;
0055: import org.springframework.web.context.request.ServletRequestAttributes;
0056: import org.springframework.web.multipart.MultipartException;
0057: import org.springframework.web.multipart.MultipartHttpServletRequest;
0058: import org.springframework.web.multipart.MultipartResolver;
0059: import org.springframework.web.util.NestedServletException;
0060: import org.springframework.web.util.UrlPathHelper;
0061: import org.springframework.web.util.WebUtils;
0062:
0063: /**
0064: * Central dispatcher for HTTP request handlers/controllers,
0065: * e.g. for web UI controllers or HTTP-based remote service exporters.
0066: * Dispatches to registered handlers for processing a web request,
0067: * providing convenient mapping and exception handling facilities.
0068: *
0069: * <p>This servlet is very flexible: It can be used with just about any workflow,
0070: * with the installation of the appropriate adapter classes. It offers the
0071: * following functionality that distinguishes it from other request-driven
0072: * web MVC frameworks:
0073: *
0074: * <ul>
0075: * <li>It is based around a JavaBeans configuration mechanism.
0076: *
0077: * <li>It can use any {@link HandlerMapping} implementation - pre-built or provided
0078: * as part of an application - to control the routing of requests to handler objects.
0079: * Default is {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}.
0080: * HandlerMapping objects can be defined as beans in the servlet's application context,
0081: * implementing the HandlerMapping interface, overriding the default HandlerMapping
0082: * if present. HandlerMappings can be given any bean name (they are tested by type).
0083: *
0084: * <li>It can use any {@link HandlerAdapter}; this allows to use any handler interface.
0085: * Default adapters are {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter},
0086: * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} and
0087: * {@link org.springframework.web.servlet.mvc.throwaway.ThrowawayControllerHandlerAdapter},
0088: * for Spring's {@link org.springframework.web.HttpRequestHandler},
0089: * {@link org.springframework.web.servlet.mvc.Controller} and
0090: * {@link org.springframework.web.servlet.mvc.throwaway.ThrowawayController} interfaces,
0091: * respectively. HandlerAdapter objects can be added as beans in the application context,
0092: * overriding the default HandlerAdapters. Like HandlerMappings, HandlerAdapters
0093: * can be given any bean name (they are tested by type).
0094: *
0095: * <li>The dispatcher's exception resolution strategy can be specified via a
0096: * {@link HandlerExceptionResolver}, for example mapping certain exceptions to
0097: * error pages. Default is none. Additional HandlerExceptionResolvers can be added
0098: * through the application context. HandlerExceptionResolver can be given any
0099: * bean name (they are tested by type).
0100: *
0101: * <li>Its view resolution strategy can be specified via a {@link ViewResolver}
0102: * implementation, resolving symbolic view names into View objects. Default is
0103: * {@link org.springframework.web.servlet.view.InternalResourceViewResolver}.
0104: * ViewResolver objects can be added as beans in the application context,
0105: * overriding the default ViewResolver. ViewResolvers can be given any bean name
0106: * (they are tested by type).
0107: *
0108: * <li>If a {@link View} or view name is not supplied by the user, then the configured
0109: * {@link RequestToViewNameTranslator} will translate the current request into a
0110: * view name. The corresponding bean name is "viewNameTranslator"; the default is
0111: * {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}.
0112: *
0113: * <li>The dispatcher's strategy for resolving multipart requests is determined by
0114: * a {@link org.springframework.web.multipart.MultipartResolver} implementation.
0115: * Implementations for Jakarta Commons FileUpload and Jason Hunter's COS are
0116: * included; the typical choise is
0117: * {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
0118: * The MultipartResolver bean name is "multipartResolver"; default is none.
0119: *
0120: * <li>Its locale resolution strategy is determined by a {@link LocaleResolver}.
0121: * Out-of-the-box implementations work via HTTP accept header, cookie, or session.
0122: * The LocaleResolver bean name is "localeResolver"; default is
0123: * {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}.
0124: *
0125: * <li>Its theme resolution strategy is determined by a {@link ThemeResolver}.
0126: * Implementations for a fixed theme and for cookie and session storage are included.
0127: * The ThemeResolver bean name is "themeResolver"; default is
0128: * {@link org.springframework.web.servlet.theme.FixedThemeResolver}.
0129: * </ul>
0130: *
0131: * <p><b>A web application can define any number of DispatcherServlets.</b>
0132: * Each servlet will operate in its own namespace, loading its own application
0133: * context with mappings, handlers, etc. Only the root application context
0134: * as loaded by {@link org.springframework.web.context.ContextLoaderListener},
0135: * if any, will be shared.
0136: *
0137: * @author Rod Johnson
0138: * @author Juergen Hoeller
0139: * @author Rob Harrop
0140: * @see org.springframework.web.HttpRequestHandler
0141: * @see org.springframework.web.servlet.mvc.Controller
0142: * @see org.springframework.web.context.ContextLoaderListener
0143: */
0144: public class DispatcherServlet extends FrameworkServlet {
0145:
0146: /**
0147: * Well-known name for the MultipartResolver object in the bean factory for this namespace.
0148: */
0149: public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
0150:
0151: /**
0152: * Well-known name for the LocaleResolver object in the bean factory for this namespace.
0153: */
0154: public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
0155:
0156: /**
0157: * Well-known name for the ThemeResolver object in the bean factory for this namespace.
0158: */
0159: public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
0160:
0161: /**
0162: * Well-known name for the HandlerMapping object in the bean factory for this namespace.
0163: * Only used when "detectAllHandlerMappings" is turned off.
0164: * @see #setDetectAllHandlerMappings
0165: */
0166: public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
0167:
0168: /**
0169: * Well-known name for the HandlerAdapter object in the bean factory for this namespace.
0170: * Only used when "detectAllHandlerAdapters" is turned off.
0171: * @see #setDetectAllHandlerAdapters
0172: */
0173: public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
0174:
0175: /**
0176: * Well-known name for the HandlerExceptionResolver object in the bean factory for this
0177: * namespace. Only used when "detectAllHandlerExceptionResolvers" is turned off.
0178: * @see #setDetectAllHandlerExceptionResolvers
0179: */
0180: public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
0181:
0182: /**
0183: * Well-known name for the RequestToViewNameTranslator object in the bean factory for
0184: * this namespace.
0185: */
0186: public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
0187:
0188: /**
0189: * Well-known name for the ViewResolver object in the bean factory for this namespace.
0190: * Only used when "detectAllViewResolvers" is turned off.
0191: * @see #setDetectAllViewResolvers
0192: */
0193: public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
0194:
0195: /**
0196: * Request attribute to hold the currently chosen HandlerExecutionChain.
0197: * Only used for internal optimizations.
0198: */
0199: public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class
0200: .getName()
0201: + ".HANDLER";
0202:
0203: /**
0204: * Request attribute to hold the current web application context.
0205: * Otherwise only the global web app context is obtainable by tags etc.
0206: * @see org.springframework.web.servlet.support.RequestContextUtils#getWebApplicationContext
0207: */
0208: public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class
0209: .getName()
0210: + ".CONTEXT";
0211:
0212: /**
0213: * Request attribute to hold the current LocaleResolver, retrievable by views.
0214: * @see org.springframework.web.servlet.support.RequestContextUtils#getLocaleResolver
0215: */
0216: public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class
0217: .getName()
0218: + ".LOCALE_RESOLVER";
0219:
0220: /**
0221: * Request attribute to hold the current ThemeResolver, retrievable by views.
0222: * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeResolver
0223: */
0224: public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class
0225: .getName()
0226: + ".THEME_RESOLVER";
0227:
0228: /**
0229: * Request attribute to hold the current ThemeSource, retrievable by views.
0230: * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource
0231: */
0232: public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class
0233: .getName()
0234: + ".THEME_SOURCE";
0235:
0236: /**
0237: * Log category to use when no mapped handler is found for a request.
0238: */
0239: public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
0240:
0241: /**
0242: * Name of the class path resource (relative to the DispatcherServlet class)
0243: * that defines DispatcherServlet's default strategy names.
0244: */
0245: private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
0246:
0247: /**
0248: * Additional logger to use when no mapped handler is found for a request.
0249: */
0250: protected static final Log pageNotFoundLogger = LogFactory
0251: .getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
0252:
0253: private static final Properties defaultStrategies;
0254:
0255: static {
0256: // Load default strategy implementations from properties file.
0257: // This is currently strictly internal and not meant to be customized
0258: // by application developers.
0259: try {
0260: ClassPathResource resource = new ClassPathResource(
0261: DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
0262: defaultStrategies = PropertiesLoaderUtils
0263: .loadProperties(resource);
0264: } catch (IOException ex) {
0265: throw new IllegalStateException(
0266: "Could not load 'DispatcherServlet.properties': "
0267: + ex.getMessage());
0268: }
0269: }
0270:
0271: /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
0272: private boolean detectAllHandlerMappings = true;
0273:
0274: /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
0275: private boolean detectAllHandlerAdapters = true;
0276:
0277: /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
0278: private boolean detectAllHandlerExceptionResolvers = true;
0279:
0280: /** Detect all ViewResolvers or just expect "viewResolver" bean? */
0281: private boolean detectAllViewResolvers = true;
0282:
0283: /** Perform cleanup of request attributes after include request? */
0284: private boolean cleanupAfterInclude = true;
0285:
0286: /** Expose LocaleContext and RequestAttributes as inheritable for child threads? */
0287: private boolean threadContextInheritable = false;
0288:
0289: /** MultipartResolver used by this servlet */
0290: private MultipartResolver multipartResolver;
0291:
0292: /** LocaleResolver used by this servlet */
0293: private LocaleResolver localeResolver;
0294:
0295: /** ThemeResolver used by this servlet */
0296: private ThemeResolver themeResolver;
0297:
0298: /** List of HandlerMappings used by this servlet */
0299: private List handlerMappings;
0300:
0301: /** List of HandlerAdapters used by this servlet */
0302: private List handlerAdapters;
0303:
0304: /** List of HandlerExceptionResolvers used by this servlet */
0305: private List handlerExceptionResolvers;
0306:
0307: /** RequestToViewNameTranslator used by this servlet */
0308: private RequestToViewNameTranslator viewNameTranslator;
0309:
0310: /** List of ViewResolvers used by this servlet */
0311: private List viewResolvers;
0312:
0313: /**
0314: * Set whether to detect all HandlerMapping beans in this servlet's context.
0315: * Else, just a single bean with name "handlerMapping" will be expected.
0316: * <p>Default is "true". Turn this off if you want this servlet to use a
0317: * single HandlerMapping, despite multiple HandlerMapping beans being
0318: * defined in the context.
0319: */
0320: public void setDetectAllHandlerMappings(
0321: boolean detectAllHandlerMappings) {
0322: this .detectAllHandlerMappings = detectAllHandlerMappings;
0323: }
0324:
0325: /**
0326: * Set whether to detect all HandlerAdapter beans in this servlet's context.
0327: * Else, just a single bean with name "handlerAdapter" will be expected.
0328: * <p>Default is "true". Turn this off if you want this servlet to use a
0329: * single HandlerAdapter, despite multiple HandlerAdapter beans being
0330: * defined in the context.
0331: */
0332: public void setDetectAllHandlerAdapters(
0333: boolean detectAllHandlerAdapters) {
0334: this .detectAllHandlerAdapters = detectAllHandlerAdapters;
0335: }
0336:
0337: /**
0338: * Set whether to detect all HandlerExceptionResolver beans in this servlet's context.
0339: * Else, just a single bean with name "handlerExceptionResolver" will be expected.
0340: * <p>Default is "true". Turn this off if you want this servlet to use a
0341: * single HandlerExceptionResolver, despite multiple HandlerExceptionResolver
0342: * beans being defined in the context.
0343: */
0344: public void setDetectAllHandlerExceptionResolvers(
0345: boolean detectAllHandlerExceptionResolvers) {
0346: this .detectAllHandlerExceptionResolvers = detectAllHandlerExceptionResolvers;
0347: }
0348:
0349: /**
0350: * Set whether to detect all ViewResolver beans in this servlet's context.
0351: * Else, just a single bean with name "viewResolver" will be expected.
0352: * <p>Default is "true". Turn this off if you want this servlet to use a
0353: * single ViewResolver, despite multiple ViewResolver beans being
0354: * defined in the context.
0355: */
0356: public void setDetectAllViewResolvers(boolean detectAllViewResolvers) {
0357: this .detectAllViewResolvers = detectAllViewResolvers;
0358: }
0359:
0360: /**
0361: * Set whether to perform cleanup of request attributes after an include request,
0362: * that is, whether to reset the original state of all request attributes after
0363: * the DispatcherServlet has processed within an include request. Else, just the
0364: * DispatcherServlet's own request attributes will be reset, but not model
0365: * attributes for JSPs or special attributes set by views (for example, JSTL's).
0366: * <p>Default is "true", which is strongly recommended. Views should not rely on
0367: * request attributes having been set by (dynamic) includes. This allows JSP views
0368: * rendered by an included controller to use any model attributes, even with the
0369: * same names as in the main JSP, without causing side effects. Only turn this
0370: * off for special needs, for example to deliberately allow main JSPs to access
0371: * attributes from JSP views rendered by an included controller.
0372: */
0373: public void setCleanupAfterInclude(boolean cleanupAfterInclude) {
0374: this .cleanupAfterInclude = cleanupAfterInclude;
0375: }
0376:
0377: /**
0378: * Set whether to expose the LocaleContext and RequestAttributes as inheritable
0379: * for child threads (using an {@link java.lang.InheritableThreadLocal}).
0380: * <p>Default is "false", to avoid side effects on spawned background threads.
0381: * Switch this to "true" to enable inheritance for custom child threads which
0382: * are spawned during request processing and only used for this request
0383: * (that is, ending after their initial task, without reuse of the thread).
0384: * <p><b>WARNING:</b> Do not use inheritance for child threads if you are
0385: * accessing a thread pool which is configured to potentially add new threads
0386: * on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}),
0387: * since this will expose the inherited context to such a pooled thread.
0388: */
0389: public void setThreadContextInheritable(
0390: boolean threadContextInheritable) {
0391: this .threadContextInheritable = threadContextInheritable;
0392: }
0393:
0394: /**
0395: * This implementation calls {@link #initStrategies}.
0396: */
0397: protected void onRefresh(ApplicationContext context)
0398: throws BeansException {
0399: initStrategies(context);
0400: }
0401:
0402: /**
0403: * Initialize the strategy objects that this servlet uses.
0404: * <p>May be overridden in subclasses in order to initialize
0405: * further strategy objects.
0406: */
0407: protected void initStrategies(ApplicationContext context) {
0408: initMultipartResolver(context);
0409: initLocaleResolver(context);
0410: initThemeResolver(context);
0411: initHandlerMappings(context);
0412: initHandlerAdapters(context);
0413: initHandlerExceptionResolvers(context);
0414: initRequestToViewNameTranslator(context);
0415: initViewResolvers(context);
0416: }
0417:
0418: /**
0419: * Initialize the MultipartResolver used by this class.
0420: * <p>If no bean is defined with the given name in the BeanFactory
0421: * for this namespace, no multipart handling is provided.
0422: */
0423: private void initMultipartResolver(ApplicationContext context) {
0424: try {
0425: this .multipartResolver = (MultipartResolver) context
0426: .getBean(MULTIPART_RESOLVER_BEAN_NAME,
0427: MultipartResolver.class);
0428: if (logger.isDebugEnabled()) {
0429: logger.debug("Using MultipartResolver ["
0430: + this .multipartResolver + "]");
0431: }
0432: } catch (NoSuchBeanDefinitionException ex) {
0433: // Default is no multipart resolver.
0434: this .multipartResolver = null;
0435: if (logger.isDebugEnabled()) {
0436: logger
0437: .debug("Unable to locate MultipartResolver with name '"
0438: + MULTIPART_RESOLVER_BEAN_NAME
0439: + "': no multipart request handling provided");
0440: }
0441: }
0442: }
0443:
0444: /**
0445: * Initialize the LocaleResolver used by this class.
0446: * <p>If no bean is defined with the given name in the BeanFactory
0447: * for this namespace, we default to AcceptHeaderLocaleResolver.
0448: */
0449: private void initLocaleResolver(ApplicationContext context) {
0450: try {
0451: this .localeResolver = (LocaleResolver) context.getBean(
0452: LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
0453: if (logger.isDebugEnabled()) {
0454: logger.debug("Using LocaleResolver ["
0455: + this .localeResolver + "]");
0456: }
0457: } catch (NoSuchBeanDefinitionException ex) {
0458: // We need to use the default.
0459: this .localeResolver = (LocaleResolver) getDefaultStrategy(
0460: context, LocaleResolver.class);
0461: if (logger.isDebugEnabled()) {
0462: logger
0463: .debug("Unable to locate LocaleResolver with name '"
0464: + LOCALE_RESOLVER_BEAN_NAME
0465: + "': using default ["
0466: + this .localeResolver + "]");
0467: }
0468: }
0469: }
0470:
0471: /**
0472: * Initialize the ThemeResolver used by this class.
0473: * <p>If no bean is defined with the given name in the BeanFactory
0474: * for this namespace, we default to a FixedThemeResolver.
0475: */
0476: private void initThemeResolver(ApplicationContext context) {
0477: try {
0478: this .themeResolver = (ThemeResolver) context.getBean(
0479: THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
0480: if (logger.isDebugEnabled()) {
0481: logger.debug("Using ThemeResolver ["
0482: + this .themeResolver + "]");
0483: }
0484: } catch (NoSuchBeanDefinitionException ex) {
0485: // We need to use the default.
0486: this .themeResolver = (ThemeResolver) getDefaultStrategy(
0487: context, ThemeResolver.class);
0488: if (logger.isDebugEnabled()) {
0489: logger
0490: .debug("Unable to locate ThemeResolver with name '"
0491: + THEME_RESOLVER_BEAN_NAME
0492: + "': using default ["
0493: + this .themeResolver + "]");
0494: }
0495: }
0496: }
0497:
0498: /**
0499: * Initialize the HandlerMappings used by this class.
0500: * <p>If no HandlerMapping beans are defined in the BeanFactory
0501: * for this namespace, we default to BeanNameUrlHandlerMapping.
0502: */
0503: private void initHandlerMappings(ApplicationContext context) {
0504: this .handlerMappings = null;
0505:
0506: if (this .detectAllHandlerMappings) {
0507: // Find all HandlerMappings in the ApplicationContext,
0508: // including ancestor contexts.
0509: Map matchingBeans = BeanFactoryUtils
0510: .beansOfTypeIncludingAncestors(context,
0511: HandlerMapping.class, true, false);
0512: if (!matchingBeans.isEmpty()) {
0513: this .handlerMappings = new ArrayList(matchingBeans
0514: .values());
0515: // We keep HandlerMappings in sorted order.
0516: Collections.sort(this .handlerMappings,
0517: new OrderComparator());
0518: }
0519: } else {
0520: try {
0521: Object hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,
0522: HandlerMapping.class);
0523: this .handlerMappings = Collections.singletonList(hm);
0524: } catch (NoSuchBeanDefinitionException ex) {
0525: // Ignore, we'll add a default HandlerMapping later.
0526: }
0527: }
0528:
0529: // Ensure we have at least one HandlerMapping, by registering
0530: // a default HandlerMapping if no other mappings are found.
0531: if (this .handlerMappings == null) {
0532: this .handlerMappings = getDefaultStrategies(context,
0533: HandlerMapping.class);
0534: if (logger.isDebugEnabled()) {
0535: logger.debug("No HandlerMappings found in servlet '"
0536: + getServletName() + "': using default");
0537: }
0538: }
0539: }
0540:
0541: /**
0542: * Initialize the HandlerAdapters used by this class.
0543: * <p>If no HandlerAdapter beans are defined in the BeanFactory
0544: * for this namespace, we default to SimpleControllerHandlerAdapter.
0545: */
0546: private void initHandlerAdapters(ApplicationContext context) {
0547: this .handlerAdapters = null;
0548:
0549: if (this .detectAllHandlerAdapters) {
0550: // Find all HandlerAdapters in the ApplicationContext,
0551: // including ancestor contexts.
0552: Map matchingBeans = BeanFactoryUtils
0553: .beansOfTypeIncludingAncestors(context,
0554: HandlerAdapter.class, true, false);
0555: if (!matchingBeans.isEmpty()) {
0556: this .handlerAdapters = new ArrayList(matchingBeans
0557: .values());
0558: // We keep HandlerAdapters in sorted order.
0559: Collections.sort(this .handlerAdapters,
0560: new OrderComparator());
0561: }
0562: } else {
0563: try {
0564: Object ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME,
0565: HandlerAdapter.class);
0566: this .handlerAdapters = Collections.singletonList(ha);
0567: } catch (NoSuchBeanDefinitionException ex) {
0568: // Ignore, we'll add a default HandlerAdapter later.
0569: }
0570: }
0571:
0572: // Ensure we have at least some HandlerAdapters, by registering
0573: // default HandlerAdapters if no other adapters are found.
0574: if (this .handlerAdapters == null) {
0575: this .handlerAdapters = getDefaultStrategies(context,
0576: HandlerAdapter.class);
0577: if (logger.isDebugEnabled()) {
0578: logger.debug("No HandlerAdapters found in servlet '"
0579: + getServletName() + "': using default");
0580: }
0581: }
0582: }
0583:
0584: /**
0585: * Initialize the HandlerExceptionResolver used by this class.
0586: * <p>If no bean is defined with the given name in the BeanFactory
0587: * for this namespace, we default to no exception resolver.
0588: */
0589: private void initHandlerExceptionResolvers(
0590: ApplicationContext context) {
0591: this .handlerExceptionResolvers = null;
0592:
0593: if (this .detectAllHandlerExceptionResolvers) {
0594: // Find all HandlerExceptionResolvers in the ApplicationContext,
0595: // including ancestor contexts.
0596: Map matchingBeans = BeanFactoryUtils
0597: .beansOfTypeIncludingAncestors(context,
0598: HandlerExceptionResolver.class, true, false);
0599: if (!matchingBeans.isEmpty()) {
0600: this .handlerExceptionResolvers = new ArrayList(
0601: matchingBeans.values());
0602: // We keep HandlerExceptionResolvers in sorted order.
0603: Collections.sort(this .handlerExceptionResolvers,
0604: new OrderComparator());
0605: }
0606: } else {
0607: try {
0608: Object her = context.getBean(
0609: HANDLER_EXCEPTION_RESOLVER_BEAN_NAME,
0610: HandlerExceptionResolver.class);
0611: this .handlerExceptionResolvers = Collections
0612: .singletonList(her);
0613: } catch (NoSuchBeanDefinitionException ex) {
0614: // Ignore, no HandlerExceptionResolver is fine too.
0615: }
0616: }
0617:
0618: // Just for consistency, check for default HandlerExceptionResolvers...
0619: // There aren't any in usual scenarios.
0620: if (this .handlerExceptionResolvers == null) {
0621: this .handlerExceptionResolvers = getDefaultStrategies(
0622: context, HandlerExceptionResolver.class);
0623: if (logger.isDebugEnabled()) {
0624: logger
0625: .debug("No HandlerExceptionResolvers found in servlet '"
0626: + getServletName() + "': using default");
0627: }
0628: }
0629: }
0630:
0631: /**
0632: * Initialize the RequestToViewNameTranslator used by this servlet instance. If no
0633: * implementation is configured then we default to DefaultRequestToViewNameTranslator.
0634: */
0635: private void initRequestToViewNameTranslator(
0636: ApplicationContext context) {
0637: try {
0638: this .viewNameTranslator = (RequestToViewNameTranslator) context
0639: .getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME,
0640: RequestToViewNameTranslator.class);
0641: if (logger.isDebugEnabled()) {
0642: logger.debug("Using RequestToViewNameTranslator ["
0643: + this .viewNameTranslator + "]");
0644: }
0645: } catch (NoSuchBeanDefinitionException ex) {
0646: // We need to use the default.
0647: this .viewNameTranslator = (RequestToViewNameTranslator) getDefaultStrategy(
0648: context, RequestToViewNameTranslator.class);
0649: if (logger.isDebugEnabled()) {
0650: logger
0651: .debug("Unable to locate RequestToViewNameTranslator with name '"
0652: + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME
0653: + "': using default ["
0654: + this .viewNameTranslator + "]");
0655: }
0656: }
0657: }
0658:
0659: /**
0660: * Initialize the ViewResolvers used by this class.
0661: * <p>If no ViewResolver beans are defined in the BeanFactory
0662: * for this namespace, we default to InternalResourceViewResolver.
0663: */
0664: private void initViewResolvers(ApplicationContext context) {
0665: this .viewResolvers = null;
0666:
0667: if (this .detectAllViewResolvers) {
0668: // Find all ViewResolvers in the ApplicationContext,
0669: // including ancestor contexts.
0670: Map matchingBeans = BeanFactoryUtils
0671: .beansOfTypeIncludingAncestors(context,
0672: ViewResolver.class, true, false);
0673: if (!matchingBeans.isEmpty()) {
0674: this .viewResolvers = new ArrayList(matchingBeans
0675: .values());
0676: // We keep ViewResolvers in sorted order.
0677: Collections.sort(this .viewResolvers,
0678: new OrderComparator());
0679: }
0680: } else {
0681: try {
0682: Object vr = context.getBean(VIEW_RESOLVER_BEAN_NAME,
0683: ViewResolver.class);
0684: this .viewResolvers = Collections.singletonList(vr);
0685: } catch (NoSuchBeanDefinitionException ex) {
0686: // Ignore, we'll add a default ViewResolver later.
0687: }
0688: }
0689:
0690: // Ensure we have at least one ViewResolver, by registering
0691: // a default ViewResolver if no other resolvers are found.
0692: if (this .viewResolvers == null) {
0693: this .viewResolvers = getDefaultStrategies(context,
0694: ViewResolver.class);
0695: if (logger.isDebugEnabled()) {
0696: logger.debug("No ViewResolvers found in servlet '"
0697: + getServletName() + "': using default");
0698: }
0699: }
0700: }
0701:
0702: /**
0703: * Return this servlet's ThemeSource, if any; else return <code>null</code>.
0704: * <p>Default is to return the WebApplicationContext as ThemeSource,
0705: * provided that it implements the ThemeSource interface.
0706: * @return the ThemeSource, if any
0707: * @see #getWebApplicationContext()
0708: */
0709: public ThemeSource getThemeSource() {
0710: if (getWebApplicationContext() instanceof ThemeSource) {
0711: return (ThemeSource) getWebApplicationContext();
0712: } else {
0713: return null;
0714: }
0715: }
0716:
0717: /**
0718: * Return the default strategy object for the given strategy interface.
0719: * <p>The default implementation delegates to {@link #getDefaultStrategies},
0720: * expecting a single object in the list.
0721: * @param context the current WebApplicationContext
0722: * @param strategyInterface the strategy interface
0723: * @return the corresponding strategy object
0724: * @throws BeansException if initialization failed
0725: * @see #getDefaultStrategies
0726: */
0727: protected Object getDefaultStrategy(ApplicationContext context,
0728: Class strategyInterface) throws BeansException {
0729: List strategies = getDefaultStrategies(context,
0730: strategyInterface);
0731: if (strategies.size() != 1) {
0732: throw new BeanInitializationException(
0733: "DispatcherServlet needs exactly 1 strategy for interface ["
0734: + strategyInterface.getName() + "]");
0735: }
0736: return strategies.get(0);
0737: }
0738:
0739: /**
0740: * Create a List of default strategy objects for the given strategy interface.
0741: * <p>The default implementation uses the "DispatcherServlet.properties" file
0742: * (in the same package as the DispatcherServlet class) to determine the class names.
0743: * It instantiates the strategy objects through the context's BeanFactory.
0744: * @param context the current WebApplicationContext
0745: * @param strategyInterface the strategy interface
0746: * @return the List of corresponding strategy objects
0747: * @throws BeansException if initialization failed
0748: */
0749: protected List getDefaultStrategies(ApplicationContext context,
0750: Class strategyInterface) throws BeansException {
0751: String key = strategyInterface.getName();
0752: List strategies = null;
0753: String value = defaultStrategies.getProperty(key);
0754: if (value != null) {
0755: String[] classNames = StringUtils
0756: .commaDelimitedListToStringArray(value);
0757: strategies = new ArrayList(classNames.length);
0758: for (int i = 0; i < classNames.length; i++) {
0759: String className = classNames[i];
0760: try {
0761: Class clazz = ClassUtils.forName(className,
0762: getClass().getClassLoader());
0763: Object strategy = createDefaultStrategy(context,
0764: clazz);
0765: strategies.add(strategy);
0766: } catch (ClassNotFoundException ex) {
0767: throw new BeanInitializationException(
0768: "Could not find DispatcherServlet's default strategy class ["
0769: + className + "] for interface ["
0770: + key + "]", ex);
0771: } catch (LinkageError err) {
0772: throw new BeanInitializationException(
0773: "Error loading DispatcherServlet's default strategy class ["
0774: + className
0775: + "] for interface ["
0776: + key
0777: + "]: problem with class file or dependent class",
0778: err);
0779: }
0780: }
0781: } else {
0782: strategies = Collections.EMPTY_LIST;
0783: }
0784: return strategies;
0785: }
0786:
0787: /**
0788: * Create a default strategy.
0789: * <p>The default implementation uses
0790: * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
0791: * @param context the current WebApplicationContext
0792: * @param clazz the strategy implementation class to instantiate
0793: * @throws BeansException if initialization failed
0794: * @return the fully configured strategy instance
0795: * @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
0796: * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
0797: */
0798: protected Object createDefaultStrategy(ApplicationContext context,
0799: Class clazz) throws BeansException {
0800: return context.getAutowireCapableBeanFactory().createBean(
0801: clazz, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
0802: }
0803:
0804: /**
0805: * Obtain this servlet's MultipartResolver, if any.
0806: * @return the MultipartResolver used by this servlet, or <code>null</code>
0807: * if none (indicating that no multipart support is available)
0808: */
0809: public MultipartResolver getMultipartResolver() {
0810: return this .multipartResolver;
0811: }
0812:
0813: /**
0814: * Exposes the DispatcherServlet-specific request attributes and
0815: * delegates to {@link #doDispatch} for the actual dispatching.
0816: */
0817: protected void doService(HttpServletRequest request,
0818: HttpServletResponse response) throws Exception {
0819: if (logger.isDebugEnabled()) {
0820: logger.debug("DispatcherServlet with name '"
0821: + getServletName() + "' received request for ["
0822: + request.getRequestURI() + "]");
0823: }
0824:
0825: // Keep a snapshot of the request attributes in case of an include,
0826: // to be able to restore the original attributes after the include.
0827: Map attributesSnapshot = null;
0828: if (WebUtils.isIncludeRequest(request)) {
0829: logger
0830: .debug("Taking snapshot of request attributes before include");
0831: attributesSnapshot = new HashMap();
0832: Enumeration attrNames = request.getAttributeNames();
0833: while (attrNames.hasMoreElements()) {
0834: String attrName = (String) attrNames.nextElement();
0835: if (this .cleanupAfterInclude
0836: || attrName.startsWith(DispatcherServlet.class
0837: .getName())) {
0838: attributesSnapshot.put(attrName, request
0839: .getAttribute(attrName));
0840: }
0841: }
0842: }
0843:
0844: // Make framework objects available to handlers and view objects.
0845: request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,
0846: getWebApplicationContext());
0847: request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE,
0848: this .localeResolver);
0849: request.setAttribute(THEME_RESOLVER_ATTRIBUTE,
0850: this .themeResolver);
0851: request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
0852:
0853: try {
0854: doDispatch(request, response);
0855: } finally {
0856: // Restore the original attribute snapshot, in case of an include.
0857: if (attributesSnapshot != null) {
0858: restoreAttributesAfterInclude(request,
0859: attributesSnapshot);
0860: }
0861: }
0862: }
0863:
0864: /**
0865: * Process the actual dispatching to the handler.
0866: * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
0867: * The HandlerAdapter will be obtained by querying the servlet's installed
0868: * HandlerAdapters to find the first that supports the handler class.
0869: * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or
0870: * handlers themselves to decide which methods are acceptable.
0871: * @param request current HTTP request
0872: * @param response current HTTP response
0873: * @throws Exception in case of any kind of processing failure
0874: */
0875: protected void doDispatch(HttpServletRequest request,
0876: HttpServletResponse response) throws Exception {
0877: HttpServletRequest processedRequest = request;
0878: HandlerExecutionChain mappedHandler = null;
0879: int interceptorIndex = -1;
0880:
0881: // Expose current LocaleResolver and request as LocaleContext.
0882: LocaleContext previousLocaleContext = LocaleContextHolder
0883: .getLocaleContext();
0884: LocaleContextHolder.setLocaleContext(
0885: buildLocaleContext(request),
0886: this .threadContextInheritable);
0887:
0888: // Expose current RequestAttributes to current thread.
0889: RequestAttributes previousRequestAttributes = RequestContextHolder
0890: .getRequestAttributes();
0891: ServletRequestAttributes requestAttributes = new ServletRequestAttributes(
0892: request);
0893: RequestContextHolder.setRequestAttributes(requestAttributes,
0894: this .threadContextInheritable);
0895:
0896: if (logger.isDebugEnabled()) {
0897: logger.debug("Bound request context to thread: " + request);
0898: }
0899:
0900: try {
0901: ModelAndView mv = null;
0902: try {
0903: processedRequest = checkMultipart(request);
0904:
0905: // Determine handler for the current request.
0906: mappedHandler = getHandler(processedRequest, false);
0907: if (mappedHandler == null
0908: || mappedHandler.getHandler() == null) {
0909: noHandlerFound(processedRequest, response);
0910: return;
0911: }
0912:
0913: // Apply preHandle methods of registered interceptors.
0914: if (mappedHandler.getInterceptors() != null) {
0915: for (int i = 0; i < mappedHandler.getInterceptors().length; i++) {
0916: HandlerInterceptor interceptor = mappedHandler
0917: .getInterceptors()[i];
0918: if (!interceptor.preHandle(processedRequest,
0919: response, mappedHandler.getHandler())) {
0920: triggerAfterCompletion(mappedHandler,
0921: interceptorIndex, processedRequest,
0922: response, null);
0923: return;
0924: }
0925: interceptorIndex = i;
0926: }
0927: }
0928:
0929: // Actually invoke the handler.
0930: HandlerAdapter ha = getHandlerAdapter(mappedHandler
0931: .getHandler());
0932: mv = ha.handle(processedRequest, response,
0933: mappedHandler.getHandler());
0934:
0935: // Apply postHandle methods of registered interceptors.
0936: if (mappedHandler.getInterceptors() != null) {
0937: for (int i = mappedHandler.getInterceptors().length - 1; i >= 0; i--) {
0938: HandlerInterceptor interceptor = mappedHandler
0939: .getInterceptors()[i];
0940: interceptor.postHandle(processedRequest,
0941: response, mappedHandler.getHandler(),
0942: mv);
0943: }
0944: }
0945: } catch (ModelAndViewDefiningException ex) {
0946: logger
0947: .debug(
0948: "ModelAndViewDefiningException encountered",
0949: ex);
0950: mv = ex.getModelAndView();
0951: } catch (Exception ex) {
0952: Object handler = (mappedHandler != null ? mappedHandler
0953: .getHandler() : null);
0954: mv = processHandlerException(processedRequest,
0955: response, handler, ex);
0956: }
0957:
0958: // Did the handler return a view to render?
0959: if (mv != null && !mv.wasCleared()) {
0960: render(mv, processedRequest, response);
0961: } else {
0962: if (logger.isDebugEnabled()) {
0963: logger
0964: .debug("Null ModelAndView returned to DispatcherServlet with name '"
0965: + getServletName()
0966: + "': assuming HandlerAdapter completed request handling");
0967: }
0968: }
0969:
0970: // Trigger after-completion for successful outcome.
0971: triggerAfterCompletion(mappedHandler, interceptorIndex,
0972: processedRequest, response, null);
0973: }
0974:
0975: catch (Exception ex) {
0976: // Trigger after-completion for thrown exception.
0977: triggerAfterCompletion(mappedHandler, interceptorIndex,
0978: processedRequest, response, ex);
0979: throw ex;
0980: } catch (Error err) {
0981: ServletException ex = new NestedServletException(
0982: "Handler processing failed", err);
0983: // Trigger after-completion for thrown exception.
0984: triggerAfterCompletion(mappedHandler, interceptorIndex,
0985: processedRequest, response, ex);
0986: throw ex;
0987: }
0988:
0989: finally {
0990: // Clean up any resources used by a multipart request.
0991: if (processedRequest != request) {
0992: cleanupMultipart(processedRequest);
0993: }
0994:
0995: // Reset thread-bound context.
0996: RequestContextHolder.setRequestAttributes(
0997: previousRequestAttributes,
0998: this .threadContextInheritable);
0999: LocaleContextHolder.setLocaleContext(previousLocaleContext,
1000: this .threadContextInheritable);
1001:
1002: // Clear request attributes.
1003: requestAttributes.requestCompleted();
1004: if (logger.isDebugEnabled()) {
1005: logger.debug("Cleared thread-bound request context: "
1006: + request);
1007: }
1008: }
1009: }
1010:
1011: /**
1012: * Override HttpServlet's <code>getLastModified</code> method to evaluate
1013: * the Last-Modified value of the mapped handler.
1014: */
1015: protected long getLastModified(HttpServletRequest request) {
1016: try {
1017: HandlerExecutionChain mappedHandler = getHandler(request,
1018: true);
1019: if (mappedHandler == null
1020: || mappedHandler.getHandler() == null) {
1021: // Ignore -> will reappear on doService.
1022: logger.debug("No handler found in getLastModified");
1023: return -1;
1024: }
1025:
1026: HandlerAdapter ha = getHandlerAdapter(mappedHandler
1027: .getHandler());
1028: long lastModified = ha.getLastModified(request,
1029: mappedHandler.getHandler());
1030: if (logger.isDebugEnabled()) {
1031: logger.debug("Last-Modified value for ["
1032: + request.getRequestURI() + "] is ["
1033: + lastModified + "]");
1034: }
1035: return lastModified;
1036: } catch (Exception ex) {
1037: // Ignore -> will reappear on doService.
1038: logger.debug("Exception thrown in getLastModified", ex);
1039: return -1;
1040: }
1041: }
1042:
1043: /**
1044: * Build a LocaleContext for the given request, exposing the request's
1045: * primary locale as current locale.
1046: * <p>The default implementation uses the dispatcher's LocaleResolver
1047: * to obtain the current locale, which might change during a request.
1048: * @param request current HTTP request
1049: * @return the corresponding LocaleContext
1050: */
1051: protected LocaleContext buildLocaleContext(
1052: final HttpServletRequest request) {
1053: return new LocaleContext() {
1054: public Locale getLocale() {
1055: return localeResolver.resolveLocale(request);
1056: }
1057: };
1058: }
1059:
1060: /**
1061: * Convert the request into a multipart request, and make multipart resolver available.
1062: * If no multipart resolver is set, simply use the existing request.
1063: * @param request current HTTP request
1064: * @return the processed request (multipart wrapper if necessary)
1065: * @see MultipartResolver#resolveMultipart
1066: */
1067: protected HttpServletRequest checkMultipart(
1068: HttpServletRequest request) throws MultipartException {
1069: if (this .multipartResolver != null
1070: && this .multipartResolver.isMultipart(request)) {
1071: if (request instanceof MultipartHttpServletRequest) {
1072: logger
1073: .debug("Request is already a MultipartHttpServletRequest - if not in a forward, "
1074: + "this typically results from an additional MultipartFilter in web.xml");
1075: } else {
1076: return this .multipartResolver.resolveMultipart(request);
1077: }
1078: }
1079: // If not returned before: return original request.
1080: return request;
1081: }
1082:
1083: /**
1084: * Clean up any resources used by the given multipart request (if any).
1085: * @param request current HTTP request
1086: * @see MultipartResolver#cleanupMultipart
1087: */
1088: protected void cleanupMultipart(HttpServletRequest request) {
1089: if (request instanceof MultipartHttpServletRequest) {
1090: this .multipartResolver
1091: .cleanupMultipart((MultipartHttpServletRequest) request);
1092: }
1093: }
1094:
1095: /**
1096: * Return the HandlerExecutionChain for this request.
1097: * Try all handler mappings in order.
1098: * @param request current HTTP request
1099: * @param cache whether to cache the HandlerExecutionChain in a request attribute
1100: * @return the HandlerExceutionChain, or <code>null</code> if no handler could be found
1101: */
1102: protected HandlerExecutionChain getHandler(
1103: HttpServletRequest request, boolean cache) throws Exception {
1104: HandlerExecutionChain handler = (HandlerExecutionChain) request
1105: .getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
1106: if (handler != null) {
1107: if (!cache) {
1108: request
1109: .removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
1110: }
1111: return handler;
1112: }
1113:
1114: Iterator it = this .handlerMappings.iterator();
1115: while (it.hasNext()) {
1116: HandlerMapping hm = (HandlerMapping) it.next();
1117: if (logger.isDebugEnabled()) {
1118: logger.debug("Testing handler map [" + hm
1119: + "] in DispatcherServlet with name '"
1120: + getServletName() + "'");
1121: }
1122: handler = hm.getHandler(request);
1123: if (handler != null) {
1124: if (cache) {
1125: request.setAttribute(
1126: HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
1127: }
1128: return handler;
1129: }
1130: }
1131: return null;
1132: }
1133:
1134: /**
1135: * No handler found -> set appropriate HTTP response status.
1136: * @param request current HTTP request
1137: * @param response current HTTP response
1138: * @throws IOException if thrown by the HttpServletResponse
1139: */
1140: protected void noHandlerFound(HttpServletRequest request,
1141: HttpServletResponse response) throws IOException {
1142: if (pageNotFoundLogger.isWarnEnabled()) {
1143: String requestUri = new UrlPathHelper()
1144: .getRequestUri(request);
1145: pageNotFoundLogger.warn("No mapping for [" + requestUri
1146: + "] in DispatcherServlet with name '"
1147: + getServletName() + "'");
1148: }
1149: response.sendError(HttpServletResponse.SC_NOT_FOUND);
1150: }
1151:
1152: /**
1153: * Return the HandlerAdapter for this handler object.
1154: * @param handler the handler object to find an adapter for
1155: * @throws ServletException if no HandlerAdapter can be found for the handler.
1156: * This is a fatal error.
1157: */
1158: protected HandlerAdapter getHandlerAdapter(Object handler)
1159: throws ServletException {
1160: Iterator it = this .handlerAdapters.iterator();
1161: while (it.hasNext()) {
1162: HandlerAdapter ha = (HandlerAdapter) it.next();
1163: if (logger.isDebugEnabled()) {
1164: logger.debug("Testing handler adapter [" + ha + "]");
1165: }
1166: if (ha.supports(handler)) {
1167: return ha;
1168: }
1169: }
1170: throw new ServletException(
1171: "No adapter for handler ["
1172: + handler
1173: + "]: Does your handler implement a supported interface like Controller?");
1174: }
1175:
1176: /**
1177: * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
1178: * @param request current HTTP request
1179: * @param response current HTTP response
1180: * @param handler the executed handler, or <code>null</code> if none chosen at the time of
1181: * the exception (for example, if multipart resolution failed)
1182: * @param ex the exception that got thrown during handler execution
1183: * @return a corresponding ModelAndView to forward to
1184: * @throws Exception if no error ModelAndView found
1185: */
1186: protected ModelAndView processHandlerException(
1187: HttpServletRequest request, HttpServletResponse response,
1188: Object handler, Exception ex) throws Exception {
1189:
1190: ModelAndView exMv = null;
1191: for (Iterator it = this .handlerExceptionResolvers.iterator(); exMv == null
1192: && it.hasNext();) {
1193: HandlerExceptionResolver resolver = (HandlerExceptionResolver) it
1194: .next();
1195: exMv = resolver.resolveException(request, response,
1196: handler, ex);
1197: }
1198: if (exMv != null) {
1199: if (logger.isDebugEnabled()) {
1200: logger.debug(
1201: "Handler execution resulted in exception - forwarding to resolved error view: "
1202: + exMv, ex);
1203: }
1204: return exMv;
1205: } else {
1206: throw ex;
1207: }
1208: }
1209:
1210: /**
1211: * Render the given ModelAndView. This is the last stage in handling a request.
1212: * It may involve resolving the view by name.
1213: * @param mv the ModelAndView to render
1214: * @param request current HTTP servlet request
1215: * @param response current HTTP servlet response
1216: * @throws Exception if there's a problem rendering the view
1217: */
1218: protected void render(ModelAndView mv, HttpServletRequest request,
1219: HttpServletResponse response) throws Exception {
1220:
1221: // Determine locale for request and apply it to the response.
1222: Locale locale = this .localeResolver.resolveLocale(request);
1223: response.setLocale(locale);
1224:
1225: View view = null;
1226:
1227: // Do we need view name translation?
1228: if (!mv.hasView()) {
1229: mv.setViewName(getDefaultViewName(request));
1230: }
1231:
1232: if (mv.isReference()) {
1233: // We need to resolve the view name.
1234: view = resolveViewName(mv.getViewName(), mv
1235: .getModelInternal(), locale, request);
1236: if (view == null) {
1237: throw new ServletException(
1238: "Could not resolve view with name '"
1239: + mv.getViewName()
1240: + "' in servlet with name '"
1241: + getServletName() + "'");
1242: }
1243: } else {
1244: // No need to lookup: the ModelAndView object contains the actual View object.
1245: view = mv.getView();
1246: if (view == null) {
1247: throw new ServletException("ModelAndView [" + mv
1248: + "] neither contains a view name nor a "
1249: + "View object in servlet with name '"
1250: + getServletName() + "'");
1251: }
1252: }
1253:
1254: // Delegate to the View object for rendering.
1255: if (logger.isDebugEnabled()) {
1256: logger.debug("Rendering view [" + view
1257: + "] in DispatcherServlet with name '"
1258: + getServletName() + "'");
1259: }
1260: view.render(mv.getModelInternal(), request, response);
1261: }
1262:
1263: /**
1264: * Translate the supplied request into a default view name.
1265: * @param request current HTTP servlet request
1266: * @return the view name
1267: * @throws Exception if view name translation failed
1268: */
1269: protected String getDefaultViewName(HttpServletRequest request)
1270: throws Exception {
1271: String viewName = this .viewNameTranslator.getViewName(request);
1272: if (viewName == null) {
1273: throw new ServletException("Could not translate request ["
1274: + request + "] into view name using ["
1275: + this .viewNameTranslator.getClass().getName()
1276: + "]");
1277: }
1278: return viewName;
1279: }
1280:
1281: /**
1282: * Resolve the given view name into a View object (to be rendered).
1283: * <p>Default implementations asks all ViewResolvers of this dispatcher.
1284: * Can be overridden for custom resolution strategies, potentially based
1285: * on specific model attributes or request parameters.
1286: * @param viewName the name of the view to resolve
1287: * @param model the model to be passed to the view
1288: * @param locale the current locale
1289: * @param request current HTTP servlet request
1290: * @return the View object, or <code>null</code> if none found
1291: * @throws Exception if the view cannot be resolved
1292: * (typically in case of problems creating an actual View object)
1293: * @see ViewResolver#resolveViewName
1294: */
1295: protected View resolveViewName(String viewName, Map model,
1296: Locale locale, HttpServletRequest request) throws Exception {
1297:
1298: for (Iterator it = this .viewResolvers.iterator(); it.hasNext();) {
1299: ViewResolver viewResolver = (ViewResolver) it.next();
1300: View view = viewResolver.resolveViewName(viewName, locale);
1301: if (view != null) {
1302: return view;
1303: }
1304: }
1305: return null;
1306: }
1307:
1308: /**
1309: * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
1310: * Will just invoke afterCompletion for all interceptors whose preHandle
1311: * invocation has successfully completed and returned true.
1312: * @param mappedHandler the mapped HandlerExecutionChain
1313: * @param interceptorIndex index of last interceptor that successfully completed
1314: * @param ex Exception thrown on handler execution, or <code>null</code> if none
1315: * @see HandlerInterceptor#afterCompletion
1316: */
1317: private void triggerAfterCompletion(
1318: HandlerExecutionChain mappedHandler, int interceptorIndex,
1319: HttpServletRequest request, HttpServletResponse response,
1320: Exception ex) throws Exception {
1321:
1322: // Apply afterCompletion methods of registered interceptors.
1323: if (mappedHandler != null) {
1324: if (mappedHandler.getInterceptors() != null) {
1325: for (int i = interceptorIndex; i >= 0; i--) {
1326: HandlerInterceptor interceptor = mappedHandler
1327: .getInterceptors()[i];
1328: try {
1329: interceptor.afterCompletion(request, response,
1330: mappedHandler.getHandler(), ex);
1331: } catch (Throwable ex2) {
1332: logger
1333: .error(
1334: "HandlerInterceptor.afterCompletion threw exception",
1335: ex2);
1336: }
1337: }
1338: }
1339: }
1340: }
1341:
1342: /**
1343: * Restore the request attributes after an include.
1344: * @param request current HTTP request
1345: * @param attributesSnapshot the snapshot of the request attributes
1346: * before the include
1347: */
1348: private void restoreAttributesAfterInclude(
1349: HttpServletRequest request, Map attributesSnapshot) {
1350: logger
1351: .debug("Restoring snapshot of request attributes after include");
1352:
1353: // Need to copy into separate Collection here, to avoid side effects
1354: // on the Enumeration when removing attributes.
1355: Set attrsToCheck = new HashSet();
1356: Enumeration attrNames = request.getAttributeNames();
1357: while (attrNames.hasMoreElements()) {
1358: String attrName = (String) attrNames.nextElement();
1359: if (this .cleanupAfterInclude
1360: || attrName.startsWith(DispatcherServlet.class
1361: .getName())) {
1362: attrsToCheck.add(attrName);
1363: }
1364: }
1365:
1366: // Iterate over the attributes to check, restoring the original value
1367: // or removing the attribute, respectively, if appropriate.
1368: for (Iterator it = attrsToCheck.iterator(); it.hasNext();) {
1369: String attrName = (String) it.next();
1370: Object attrValue = attributesSnapshot.get(attrName);
1371: if (attrValue != null) {
1372: if (logger.isDebugEnabled()) {
1373: logger
1374: .debug("Restoring original value of attribute ["
1375: + attrName + "] after include");
1376: }
1377: request.setAttribute(attrName, attrValue);
1378: } else {
1379: if (logger.isDebugEnabled()) {
1380: logger.debug("Removing attribute [" + attrName
1381: + "] after include");
1382: }
1383: request.removeAttribute(attrName);
1384: }
1385: }
1386: }
1387:
1388: }
|