1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.web.servlet;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Enumeration;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Properties;
30 import java.util.Set;
31 import javax.servlet.ServletException;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletResponse;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.springframework.beans.BeansException;
38 import org.springframework.beans.factory.BeanFactoryUtils;
39 import org.springframework.beans.factory.BeanInitializationException;
40 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
41 import org.springframework.context.ApplicationContext;
42 import org.springframework.context.i18n.LocaleContext;
43 import org.springframework.context.i18n.LocaleContextHolder;
44 import org.springframework.core.JdkVersion;
45 import org.springframework.core.OrderComparator;
46 import org.springframework.core.io.ClassPathResource;
47 import org.springframework.core.io.support.PropertiesLoaderUtils;
48 import org.springframework.ui.context.ThemeSource;
49 import org.springframework.util.ClassUtils;
50 import org.springframework.util.StringUtils;
51 import org.springframework.web.HttpRequestMethodNotSupportedException;
52 import org.springframework.web.context.request.RequestAttributes;
53 import org.springframework.web.context.request.RequestContextHolder;
54 import org.springframework.web.context.request.ServletRequestAttributes;
55 import org.springframework.web.multipart.MultipartException;
56 import org.springframework.web.multipart.MultipartHttpServletRequest;
57 import org.springframework.web.multipart.MultipartResolver;
58 import org.springframework.web.util.NestedServletException;
59 import org.springframework.web.util.UrlPathHelper;
60 import org.springframework.web.util.WebUtils;
61
62 /**
63 * Central dispatcher for HTTP request handlers/controllers,
64 * e.g. for web UI controllers or HTTP-based remote service exporters.
65 * Dispatches to registered handlers for processing a web request,
66 * providing convenient mapping and exception handling facilities.
67 *
68 * <p>This servlet is very flexible: It can be used with just about any workflow,
69 * with the installation of the appropriate adapter classes. It offers the
70 * following functionality that distinguishes it from other request-driven
71 * web MVC frameworks:
72 *
73 * <ul>
74 * <li>It is based around a JavaBeans configuration mechanism.
75 *
76 * <li>It can use any {@link HandlerMapping} implementation - pre-built or provided
77 * as part of an application - to control the routing of requests to handler objects.
78 * Default is {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}, as well
79 * as a {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}
80 * when running on Java 5+. HandlerMapping objects can be defined as beans in the servlet's
81 * application context, implementing the HandlerMapping interface, overriding the default
82 * HandlerMapping if present. HandlerMappings can be given any bean name (they are tested by type).
83 *
84 * <li>It can use any {@link HandlerAdapter}; this allows for using any handler interface.
85 * Default adapters are {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter},
86 * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} and
87 * {@link org.springframework.web.servlet.mvc.throwaway.ThrowawayControllerHandlerAdapter},
88 * for Spring's {@link org.springframework.web.HttpRequestHandler},
89 * {@link org.springframework.web.servlet.mvc.Controller} and
90 * {@link org.springframework.web.servlet.mvc.throwaway.ThrowawayController} interfaces,
91 * respectively. When running in a Java 5+ environment, a default
92 * {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
93 * will be registered as well. HandlerAdapter objects can be added as beans in the
94 * application context, overriding the default HandlerAdapters. Like HandlerMappings,
95 * HandlerAdapters can be given any bean name (they are tested by type).
96 *
97 * <li>The dispatcher's exception resolution strategy can be specified via a
98 * {@link HandlerExceptionResolver}, for example mapping certain exceptions to
99 * error pages. Default is none. Additional HandlerExceptionResolvers can be added
100 * through the application context. HandlerExceptionResolver can be given any
101 * bean name (they are tested by type).
102 *
103 * <li>Its view resolution strategy can be specified via a {@link ViewResolver}
104 * implementation, resolving symbolic view names into View objects. Default is
105 * {@link org.springframework.web.servlet.view.InternalResourceViewResolver}.
106 * ViewResolver objects can be added as beans in the application context,
107 * overriding the default ViewResolver. ViewResolvers can be given any bean name
108 * (they are tested by type).
109 *
110 * <li>If a {@link View} or view name is not supplied by the user, then the configured
111 * {@link RequestToViewNameTranslator} will translate the current request into a
112 * view name. The corresponding bean name is "viewNameTranslator"; the default is
113 * {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}.
114 *
115 * <li>The dispatcher's strategy for resolving multipart requests is determined by
116 * a {@link org.springframework.web.multipart.MultipartResolver} implementation.
117 * Implementations for Jakarta Commons FileUpload and Jason Hunter's COS are
118 * included; the typical choise is
119 * {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
120 * The MultipartResolver bean name is "multipartResolver"; default is none.
121 *
122 * <li>Its locale resolution strategy is determined by a {@link LocaleResolver}.
123 * Out-of-the-box implementations work via HTTP accept header, cookie, or session.
124 * The LocaleResolver bean name is "localeResolver"; default is
125 * {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}.
126 *
127 * <li>Its theme resolution strategy is determined by a {@link ThemeResolver}.
128 * Implementations for a fixed theme and for cookie and session storage are included.
129 * The ThemeResolver bean name is "themeResolver"; default is
130 * {@link org.springframework.web.servlet.theme.FixedThemeResolver}.
131 * </ul>
132 *
133 * <p><b>NOTE: The <code>@RequestMapping</code> annotation will only be processed
134 * if a corresponding <code>HandlerMapping</code> (for type level annotations)
135 * and/or <code>HandlerAdapter</code> (for method level annotations)
136 * is present in the dispatcher.</b> This is the case by default.
137 * However, if you are defining custom <code>HandlerMappings</code> or
138 * <code>HandlerAdapters</code>, then you need to make sure that a
139 * corresponding custom <code>DefaultAnnotationHandlerMapping</code>
140 * and/or <code>AnnotationMethodHandlerAdapter</code> is defined as well
141 * - provided that you intend to use <code>@RequestMapping</code>.
142 *
143 * <p><b>A web application can define any number of DispatcherServlets.</b>
144 * Each servlet will operate in its own namespace, loading its own application
145 * context with mappings, handlers, etc. Only the root application context
146 * as loaded by {@link org.springframework.web.context.ContextLoaderListener},
147 * if any, will be shared.
148 *
149 * @author Rod Johnson
150 * @author Juergen Hoeller
151 * @author Rob Harrop
152 * @see org.springframework.web.HttpRequestHandler
153 * @see org.springframework.web.servlet.mvc.Controller
154 * @see org.springframework.web.context.ContextLoaderListener
155 */
156 public class DispatcherServlet extends FrameworkServlet {
157
158 /**
159 * Well-known name for the MultipartResolver object in the bean factory for this namespace.
160 */
161 public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
162
163 /**
164 * Well-known name for the LocaleResolver object in the bean factory for this namespace.
165 */
166 public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
167
168 /**
169 * Well-known name for the ThemeResolver object in the bean factory for this namespace.
170 */
171 public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
172
173 /**
174 * Well-known name for the HandlerMapping object in the bean factory for this namespace.
175 * Only used when "detectAllHandlerMappings" is turned off.
176 * @see #setDetectAllHandlerMappings
177 */
178 public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
179
180 /**
181 * Well-known name for the HandlerAdapter object in the bean factory for this namespace.
182 * Only used when "detectAllHandlerAdapters" is turned off.
183 * @see #setDetectAllHandlerAdapters
184 */
185 public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
186
187 /**
188 * Well-known name for the HandlerExceptionResolver object in the bean factory for this
189 * namespace. Only used when "detectAllHandlerExceptionResolvers" is turned off.
190 * @see #setDetectAllHandlerExceptionResolvers
191 */
192 public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
193
194 /**
195 * Well-known name for the RequestToViewNameTranslator object in the bean factory for
196 * this namespace.
197 */
198 public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
199
200 /**
201 * Well-known name for the ViewResolver object in the bean factory for this namespace.
202 * Only used when "detectAllViewResolvers" is turned off.
203 * @see #setDetectAllViewResolvers
204 */
205 public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
206
207 /**
208 * Request attribute to hold the currently chosen HandlerExecutionChain.
209 * Only used for internal optimizations.
210 */
211 public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class.getName() + ".HANDLER";
212
213 /**
214 * Request attribute to hold the current web application context.
215 * Otherwise only the global web app context is obtainable by tags etc.
216 * @see org.springframework.web.servlet.support.RequestContextUtils#getWebApplicationContext
217 */
218 public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
219
220 /**
221 * Request attribute to hold the current LocaleResolver, retrievable by views.
222 * @see org.springframework.web.servlet.support.RequestContextUtils#getLocaleResolver
223 */
224 public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
225
226 /**
227 * Request attribute to hold the current ThemeResolver, retrievable by views.
228 * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeResolver
229 */
230 public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
231
232 /**
233 * Request attribute to hold the current ThemeSource, retrievable by views.
234 * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource
235 */
236 public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
237
238
239 /**
240 * Log category to use when no mapped handler is found for a request.
241 */
242 public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
243
244 /**
245 * Name of the class path resource (relative to the DispatcherServlet class)
246 * that defines DispatcherServlet's default strategy names.
247 */
248 private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
249
250
251 /**
252 * Additional logger to use when no mapped handler is found for a request.
253 */
254 protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
255
256 private static final Properties defaultStrategies;
257
258 static {
259 // Load default strategy implementations from properties file.
260 // This is currently strictly internal and not meant to be customized
261 // by application developers.
262 try {
263 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
264 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
265 }
266 catch (IOException ex) {
267 throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
268 }
269 }
270
271
272 /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
273 private boolean detectAllHandlerMappings = true;
274
275 /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
276 private boolean detectAllHandlerAdapters = true;
277
278 /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
279 private boolean detectAllHandlerExceptionResolvers = true;
280
281 /** Detect all ViewResolvers or just expect "viewResolver" bean? */
282 private boolean detectAllViewResolvers = true;
283
284 /** Perform cleanup of request attributes after include request? */
285 private boolean cleanupAfterInclude = true;
286
287 /** Expose LocaleContext and RequestAttributes as inheritable for child threads? */
288 private boolean threadContextInheritable = false;
289
290
291 /** MultipartResolver used by this servlet */
292 private MultipartResolver multipartResolver;
293
294 /** LocaleResolver used by this servlet */
295 private LocaleResolver localeResolver;
296
297 /** ThemeResolver used by this servlet */
298 private ThemeResolver themeResolver;
299
300 /** List of HandlerMappings used by this servlet */
301 private List handlerMappings;
302
303 /** List of HandlerAdapters used by this servlet */
304 private List handlerAdapters;
305
306 /** List of HandlerExceptionResolvers used by this servlet */
307 private List handlerExceptionResolvers;
308
309 /** RequestToViewNameTranslator used by this servlet */
310 private RequestToViewNameTranslator viewNameTranslator;
311
312 /** List of ViewResolvers used by this servlet */
313 private List viewResolvers;
314
315
316 /**
317 * Set whether to detect all HandlerMapping beans in this servlet's context.
318 * Else, just a single bean with name "handlerMapping" will be expected.
319 * <p>Default is "true". Turn this off if you want this servlet to use a
320 * single HandlerMapping, despite multiple HandlerMapping beans being
321 * defined in the context.
322 */
323 public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) {
324 this.detectAllHandlerMappings = detectAllHandlerMappings;
325 }
326
327 /**
328 * Set whether to detect all HandlerAdapter beans in this servlet's context.
329 * Else, just a single bean with name "handlerAdapter" will be expected.
330 * <p>Default is "true". Turn this off if you want this servlet to use a
331 * single HandlerAdapter, despite multiple HandlerAdapter beans being
332 * defined in the context.
333 */
334 public void setDetectAllHandlerAdapters(boolean detectAllHandlerAdapters) {
335 this.detectAllHandlerAdapters = detectAllHandlerAdapters;
336 }
337
338 /**
339 * Set whether to detect all HandlerExceptionResolver beans in this servlet's context.
340 * Else, just a single bean with name "handlerExceptionResolver" will be expected.
341 * <p>Default is "true". Turn this off if you want this servlet to use a
342 * single HandlerExceptionResolver, despite multiple HandlerExceptionResolver
343 * beans being defined in the context.
344 */
345 public void setDetectAllHandlerExceptionResolvers(boolean detectAllHandlerExceptionResolvers) {
346 this.detectAllHandlerExceptionResolvers = detectAllHandlerExceptionResolvers;
347 }
348
349 /**
350 * Set whether to detect all ViewResolver beans in this servlet's context.
351 * Else, just a single bean with name "viewResolver" will be expected.
352 * <p>Default is "true". Turn this off if you want this servlet to use a
353 * single ViewResolver, despite multiple ViewResolver beans being
354 * defined in the context.
355 */
356 public void setDetectAllViewResolvers(boolean detectAllViewResolvers) {
357 this.detectAllViewResolvers = detectAllViewResolvers;
358 }
359
360 /**
361 * Set whether to perform cleanup of request attributes after an include request,
362 * that is, whether to reset the original state of all request attributes after
363 * the DispatcherServlet has processed within an include request. Else, just the
364 * DispatcherServlet's own request attributes will be reset, but not model
365 * attributes for JSPs or special attributes set by views (for example, JSTL's).
366 * <p>Default is "true", which is strongly recommended. Views should not rely on
367 * request attributes having been set by (dynamic) includes. This allows JSP views
368 * rendered by an included controller to use any model attributes, even with the
369 * same names as in the main JSP, without causing side effects. Only turn this
370 * off for special needs, for example to deliberately allow main JSPs to access
371 * attributes from JSP views rendered by an included controller.
372 */
373 public void setCleanupAfterInclude(boolean cleanupAfterInclude) {
374 this.cleanupAfterInclude = cleanupAfterInclude;
375 }
376
377 /**
378 * Set whether to expose the LocaleContext and RequestAttributes as inheritable
379 * for child threads (using an {@link java.lang.InheritableThreadLocal}).
380 * <p>Default is "false", to avoid side effects on spawned background threads.
381 * Switch this to "true" to enable inheritance for custom child threads which
382 * are spawned during request processing and only used for this request
383 * (that is, ending after their initial task, without reuse of the thread).
384 * <p><b>WARNING:</b> Do not use inheritance for child threads if you are
385 * accessing a thread pool which is configured to potentially add new threads
386 * on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}),
387 * since this will expose the inherited context to such a pooled thread.
388 */
389 public void setThreadContextInheritable(boolean threadContextInheritable) {
390 this.threadContextInheritable = threadContextInheritable;
391 }
392
393
394 /**
395 * This implementation calls {@link #initStrategies}.
396 */
397 protected void onRefresh(ApplicationContext context) throws BeansException {
398 initStrategies(context);
399 }
400
401 /**
402 * Initialize the strategy objects that this servlet uses.
403 * <p>May be overridden in subclasses in order to initialize
404 * further strategy objects.
405 */
406 protected void initStrategies(ApplicationContext context) {
407 initMultipartResolver(context);
408 initLocaleResolver(context);
409 initThemeResolver(context);
410 initHandlerMappings(context);
411 initHandlerAdapters(context);
412 initHandlerExceptionResolvers(context);
413 initRequestToViewNameTranslator(context);
414 initViewResolvers(context);
415 }
416
417 /**
418 * Initialize the MultipartResolver used by this class.
419 * <p>If no bean is defined with the given name in the BeanFactory
420 * for this namespace, no multipart handling is provided.
421 */
422 private void initMultipartResolver(ApplicationContext context) {
423 try {
424 this.multipartResolver = (MultipartResolver)
425 context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
426 if (logger.isDebugEnabled()) {
427 logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
428 }
429 }
430 catch (NoSuchBeanDefinitionException ex) {
431 // Default is no multipart resolver.
432 this.multipartResolver = null;
433 if (logger.isDebugEnabled()) {
434 logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
435 "': no multipart request handling provided");
436 }
437 }
438 }
439
440 /**
441 * Initialize the LocaleResolver used by this class.
442 * <p>If no bean is defined with the given name in the BeanFactory
443 * for this namespace, we default to AcceptHeaderLocaleResolver.
444 */
445 private void initLocaleResolver(ApplicationContext context) {
446 try {
447 this.localeResolver = (LocaleResolver)
448 context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
449 if (logger.isDebugEnabled()) {
450 logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
451 }
452 }
453 catch (NoSuchBeanDefinitionException ex) {
454 // We need to use the default.
455 this.localeResolver = (LocaleResolver) getDefaultStrategy(context, LocaleResolver.class);
456 if (logger.isDebugEnabled()) {
457 logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
458 "': using default [" + this.localeResolver + "]");
459 }
460 }
461 }
462
463 /**
464 * Initialize the ThemeResolver used by this class.
465 * <p>If no bean is defined with the given name in the BeanFactory
466 * for this namespace, we default to a FixedThemeResolver.
467 */
468 private void initThemeResolver(ApplicationContext context) {
469 try {
470 this.themeResolver = (ThemeResolver)
471 context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
472 if (logger.isDebugEnabled()) {
473 logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
474 }
475 }
476 catch (NoSuchBeanDefinitionException ex) {
477 // We need to use the default.
478 this.themeResolver = (ThemeResolver) getDefaultStrategy(context, ThemeResolver.class);
479 if (logger.isDebugEnabled()) {
480 logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME +
481 "': using default [" + this.themeResolver + "]");
482 }
483 }
484 }
485
486 /**
487 * Initialize the HandlerMappings used by this class.
488 * <p>If no HandlerMapping beans are defined in the BeanFactory
489 * for this namespace, we default to BeanNameUrlHandlerMapping.
490 */
491 private void initHandlerMappings(ApplicationContext context) {
492 this.handlerMappings = null;
493
494 if (this.detectAllHandlerMappings) {
495 // Find all HandlerMappings in the ApplicationContext,
496 // including ancestor contexts.
497 Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
498 context, HandlerMapping.class, true, false);
499 if (!matchingBeans.isEmpty()) {
500 this.handlerMappings = new ArrayList(matchingBeans.values());
501 // We keep HandlerMappings in sorted order.
502 Collections.sort(this.handlerMappings, new OrderComparator());
503 }
504 }
505 else {
506 try {
507 Object hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
508 this.handlerMappings = Collections.singletonList(hm);
509 }
510 catch (NoSuchBeanDefinitionException ex) {
511 // Ignore, we'll add a default HandlerMapping later.
512 }
513 }
514
515 // Ensure we have at least one HandlerMapping, by registering
516 // a default HandlerMapping if no other mappings are found.
517 if (this.handlerMappings == null) {
518 this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
519 if (logger.isDebugEnabled()) {
520 logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
521 }
522 }
523 }
524
525 /**
526 * Initialize the HandlerAdapters used by this class.
527 * <p>If no HandlerAdapter beans are defined in the BeanFactory
528 * for this namespace, we default to SimpleControllerHandlerAdapter.
529 */
530 private void initHandlerAdapters(ApplicationContext context) {
531 this.handlerAdapters = null;
532
533 if (this.detectAllHandlerAdapters) {
534 // Find all HandlerAdapters in the ApplicationContext,
535 // including ancestor contexts.
536 Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
537 context, HandlerAdapter.class, true, false);
538 if (!matchingBeans.isEmpty()) {
539 this.handlerAdapters = new ArrayList(matchingBeans.values());
540 // We keep HandlerAdapters in sorted order.
541 Collections.sort(this.handlerAdapters, new OrderComparator());
542 }
543 }
544 else {
545 try {
546 Object ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
547 this.handlerAdapters = Collections.singletonList(ha);
548 }
549 catch (NoSuchBeanDefinitionException ex) {
550 // Ignore, we'll add a default HandlerAdapter later.
551 }
552 }
553
554 // Ensure we have at least some HandlerAdapters, by registering
555 // default HandlerAdapters if no other adapters are found.
556 if (this.handlerAdapters == null) {
557 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
558 if (logger.isDebugEnabled()) {
559 logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
560 }
561 }
562 }
563
564 /**
565 * Initialize the HandlerExceptionResolver used by this class.
566 * <p>If no bean is defined with the given name in the BeanFactory
567 * for this namespace, we default to no exception resolver.
568 */
569 private void initHandlerExceptionResolvers(ApplicationContext context) {
570 this.handlerExceptionResolvers = null;
571
572 if (this.detectAllHandlerExceptionResolvers) {
573 // Find all HandlerExceptionResolvers in the ApplicationContext,
574 // including ancestor contexts.
575 Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
576 context, HandlerExceptionResolver.class, true, false);
577 if (!matchingBeans.isEmpty()) {
578 this.handlerExceptionResolvers = new ArrayList(matchingBeans.values());
579 // We keep HandlerExceptionResolvers in sorted order.
580 Collections.sort(this.handlerExceptionResolvers, new OrderComparator());
581 }
582 }
583 else {
584 try {
585 Object her = context.getBean(
586 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
587 this.handlerExceptionResolvers = Collections.singletonList(her);
588 }
589 catch (NoSuchBeanDefinitionException ex) {
590 // Ignore, no HandlerExceptionResolver is fine too.
591 }
592 }
593
594 // Just for consistency, check for default HandlerExceptionResolvers...
595 // There aren't any in usual scenarios.
596 if (this.handlerExceptionResolvers == null) {
597 this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
598 if (logger.isDebugEnabled()) {
599 logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
600 }
601 }
602 }
603
604 /**
605 * Initialize the RequestToViewNameTranslator used by this servlet instance. If no
606 * implementation is configured then we default to DefaultRequestToViewNameTranslator.
607 */
608 private void initRequestToViewNameTranslator(ApplicationContext context) {
609 try {
610 this.viewNameTranslator = (RequestToViewNameTranslator) context.getBean(
611 REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
612 if (logger.isDebugEnabled()) {
613 logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
614 }
615 }
616 catch (NoSuchBeanDefinitionException ex) {
617 // We need to use the default.
618 this.viewNameTranslator =
619 (RequestToViewNameTranslator) getDefaultStrategy(context, RequestToViewNameTranslator.class);
620 if (logger.isDebugEnabled()) {
621 logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
622 REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
623 "': using default [" + this.viewNameTranslator + "]");
624 }
625 }
626 }
627
628 /**
629 * Initialize the ViewResolvers used by this class.
630 * <p>If no ViewResolver beans are defined in the BeanFactory
631 * for this namespace, we default to InternalResourceViewResolver.
632 */
633 private void initViewResolvers(ApplicationContext context) {
634 this.viewResolvers = null;
635
636 if (this.detectAllViewResolvers) {
637 // Find all ViewResolvers in the ApplicationContext,
638 // including ancestor contexts.
639 Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
640 context, ViewResolver.class, true, false);
641 if (!matchingBeans.isEmpty()) {
642 this.viewResolvers = new ArrayList(matchingBeans.values());
643 // We keep ViewResolvers in sorted order.
644 Collections.sort(this.viewResolvers, new OrderComparator());
645 }
646 }
647 else {
648 try {
649 Object vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
650 this.viewResolvers = Collections.singletonList(vr);
651 }
652 catch (NoSuchBeanDefinitionException ex) {
653 // Ignore, we'll add a default ViewResolver later.
654 }
655 }
656
657 // Ensure we have at least one ViewResolver, by registering
658 // a default ViewResolver if no other resolvers are found.
659 if (this.viewResolvers == null) {
660 this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
661 if (logger.isDebugEnabled()) {
662 logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
663 }
664 }
665 }
666
667 /**
668 * Return this servlet's ThemeSource, if any; else return <code>null</code>.
669 * <p>Default is to return the WebApplicationContext as ThemeSource,
670 * provided that it implements the ThemeSource interface.
671 * @return the ThemeSource, if any
672 * @see #getWebApplicationContext()
673 */
674 public final ThemeSource getThemeSource() {
675 if (getWebApplicationContext() instanceof ThemeSource) {
676 return (ThemeSource) getWebApplicationContext();
677 }
678 else {
679 return null;
680 }
681 }
682
683 /**
684 * Obtain this servlet's MultipartResolver, if any.
685 * @return the MultipartResolver used by this servlet, or <code>null</code>
686 * if none (indicating that no multipart support is available)
687 */
688 public final MultipartResolver getMultipartResolver() {
689 return this.multipartResolver;
690 }
691
692
693 /**
694 * Return the default strategy object for the given strategy interface.
695 * <p>The default implementation delegates to {@link #getDefaultStrategies},
696 * expecting a single object in the list.
697 * @param context the current WebApplicationContext
698 * @param strategyInterface the strategy interface
699 * @return the corresponding strategy object
700 * @throws BeansException if initialization failed
701 * @see #getDefaultStrategies
702 */
703 protected Object getDefaultStrategy(ApplicationContext context, Class strategyInterface) throws BeansException {
704 List strategies = getDefaultStrategies(context, strategyInterface);
705 if (strategies.size() != 1) {
706 throw new BeanInitializationException(
707 "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
708 }
709 return strategies.get(0);
710 }
711
712 /**
713 * Create a List of default strategy objects for the given strategy interface.
714 * <p>The default implementation uses the "DispatcherServlet.properties" file
715 * (in the same package as the DispatcherServlet class) to determine the class names.
716 * It instantiates the strategy objects through the context's BeanFactory.
717 * @param context the current WebApplicationContext
718 * @param strategyInterface the strategy interface
719 * @return the List of corresponding strategy objects
720 * @throws BeansException if initialization failed
721 */
722 protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface) throws BeansException {
723 String key = strategyInterface.getName();
724 List strategies = null;
725 String value = defaultStrategies.getProperty(key);
726 if (value != null) {
727 String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
728 strategies = new ArrayList(classNames.length);
729 for (int i = 0; i < classNames.length; i++) {
730 String className = classNames[i];
731 if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15 && className.indexOf("Annotation") != -1) {
732 // Skip Java 5 specific strategies when running on JDK 1.4...
733 continue;
734 }
735 try {
736 Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
737 Object strategy = createDefaultStrategy(context, clazz);
738 strategies.add(strategy);
739 }
740 catch (ClassNotFoundException ex) {
741 throw new BeanInitializationException(
742 "Could not find DispatcherServlet's default strategy class [" + className +
743 "] for interface [" + key + "]", ex);
744 }
745 catch (LinkageError err) {
746 throw new BeanInitializationException(
747 "Error loading DispatcherServlet's default strategy class [" + className +
748 "] for interface [" + key + "]: problem with class file or dependent class", err);
749 }
750 }
751 }
752 else {
753 strategies = Collections.EMPTY_LIST;
754 }
755 return strategies;
756 }
757
758 /**
759 * Create a default strategy.
760 * <p>The default implementation uses
761 * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
762 * @param context the current WebApplicationContext
763 * @param clazz the strategy implementation class to instantiate
764 * @throws BeansException if initialization failed
765 * @return the fully configured strategy instance
766 * @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
767 * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
768 */
769 protected Object createDefaultStrategy(ApplicationContext context, Class clazz) throws BeansException {
770 return context.getAutowireCapableBeanFactory().createBean(clazz);
771 }
772
773
774 /**
775 * Exposes the DispatcherServlet-specific request attributes and
776 * delegates to {@link #doDispatch} for the actual dispatching.
777 */
778 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
779 if (logger.isDebugEnabled()) {
780 String requestUri = new UrlPathHelper().getRequestUri(request);
781 logger.debug("DispatcherServlet with name '" + getServletName() +
782 "' processing request for [" + requestUri + "]");
783 }
784
785 // Keep a snapshot of the request attributes in case of an include,
786 // to be able to restore the original attributes after the include.
787 Map attributesSnapshot = null;
788 if (WebUtils.isIncludeRequest(request)) {
789 logger.debug("Taking snapshot of request attributes before include");
790 attributesSnapshot = new HashMap();
791 Enumeration attrNames = request.getAttributeNames();
792 while (attrNames.hasMoreElements()) {
793 String attrName = (String) attrNames.nextElement();
794 if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
795 attributesSnapshot.put(attrName, request.getAttribute(attrName));
796 }
797 }
798 }
799
800 // Make framework objects available to handlers and view objects.
801 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
802 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
803 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
804 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
805
806 try {
807 doDispatch(request, response);
808 }
809 finally {
810 // Restore the original attribute snapshot, in case of an include.
811 if (attributesSnapshot != null) {
812 restoreAttributesAfterInclude(request, attributesSnapshot);
813 }
814 }
815 }
816
817 /**
818 * Process the actual dispatching to the handler.
819 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
820 * The HandlerAdapter will be obtained by querying the servlet's installed
821 * HandlerAdapters to find the first that supports the handler class.
822 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or
823 * handlers themselves to decide which methods are acceptable.
824 * @param request current HTTP request
825 * @param response current HTTP response
826 * @throws Exception in case of any kind of processing failure
827 */
828 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
829 HttpServletRequest processedRequest = request;
830 HandlerExecutionChain mappedHandler = null;
831 int interceptorIndex = -1;
832
833 // Expose current LocaleResolver and request as LocaleContext.
834 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
835 LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
836
837 // Expose current RequestAttributes to current thread.
838 RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
839 ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
840 RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
841
842 if (logger.isTraceEnabled()) {
843 logger.trace("Bound request context to thread: " + request);
844 }
845
846 try {
847 ModelAndView mv = null;
848 boolean errorView = false;
849
850 try {
851 processedRequest = checkMultipart(request);
852
853 // Determine handler for the current request.
854 mappedHandler = getHandler(processedRequest, false);
855 if (mappedHandler == null || mappedHandler.getHandler() == null) {
856 noHandlerFound(processedRequest, response);
857 return;
858 }
859
860 // Apply preHandle methods of registered interceptors.
861 HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
862 if (interceptors != null) {
863 for (int i = 0; i < interceptors.length; i++) {
864 HandlerInterceptor interceptor = interceptors[i];
865 if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
866 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
867 return;
868 }
869 interceptorIndex = i;
870 }
871 }
872
873 // Actually invoke the handler.
874 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
875 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
876
877 // Do we need view name translation?
878 if (mv != null && !mv.hasView()) {
879 mv.setViewName(getDefaultViewName(request));
880 }
881
882 // Apply postHandle methods of registered interceptors.
883 if (interceptors != null) {
884 for (int i = interceptors.length - 1; i >= 0; i--) {
885 HandlerInterceptor interceptor = interceptors[i];
886 interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
887 }
888 }
889 }
890 catch (ModelAndViewDefiningException ex) {
891 logger.debug("ModelAndViewDefiningException encountered", ex);
892 mv = ex.getModelAndView();
893 }
894 catch (Exception ex) {
895 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
896 mv = processHandlerException(processedRequest, response, handler, ex);
897 errorView = (mv != null);
898 }
899
900 // Did the handler return a view to render?
901 if (mv != null && !mv.wasCleared()) {
902 render(mv, processedRequest, response);
903 if (errorView) {
904 WebUtils.clearErrorRequestAttributes(request);
905 }
906 }
907 else {
908 if (logger.isDebugEnabled()) {
909 logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
910 getServletName() + "': assuming HandlerAdapter completed request handling");
911 }
912 }
913
914 // Trigger after-completion for successful outcome.
915 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
916 }
917
918 catch (Exception ex) {
919 // Trigger after-completion for thrown exception.
920 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
921 throw ex;
922 }
923 catch (Error err) {
924 ServletException ex = new NestedServletException("Handler processing failed", err);
925 // Trigger after-completion for thrown exception.
926 triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
927 throw ex;
928 }
929
930 finally {
931 // Clean up any resources used by a multipart request.
932 if (processedRequest != request) {
933 cleanupMultipart(processedRequest);
934 }
935
936 // Reset thread-bound context.
937 RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
938 LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
939
940 // Clear request attributes.
941 requestAttributes.requestCompleted();
942 if (logger.isTraceEnabled()) {
943 logger.trace("Cleared thread-bound request context: " + request);
944 }
945 }
946 }
947
948 /**
949 * Override HttpServlet's <code>getLastModified</code> method to evaluate
950 * the Last-Modified value of the mapped handler.
951 */
952 protected long getLastModified(HttpServletRequest request) {
953 if (logger.isDebugEnabled()) {
954 String requestUri = new UrlPathHelper().getRequestUri(request);
955 logger.debug("DispatcherServlet with name '" + getServletName() +
956 "' determining Last-Modified value for [" + requestUri + "]");
957 }
958 try {
959 HandlerExecutionChain mappedHandler = getHandler(request, true);
960 if (mappedHandler == null || mappedHandler.getHandler() == null) {
961 // Ignore -> will reappear on doService.
962 logger.debug("No handler found in getLastModified");
963 return -1;
964 }
965
966 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
967 long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
968 if (logger.isDebugEnabled()) {
969 String requestUri = new UrlPathHelper().getRequestUri(request);
970 logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
971 }
972 return lastModified;
973 }
974 catch (Exception ex) {
975 // Ignore -> will reappear on doService.
976 logger.debug("Exception thrown in getLastModified", ex);
977 return -1;
978 }
979 }
980
981
982 /**
983 * Build a LocaleContext for the given request, exposing the request's
984 * primary locale as current locale.
985 * <p>The default implementation uses the dispatcher's LocaleResolver
986 * to obtain the current locale, which might change during a request.
987 * @param request current HTTP request
988 * @return the corresponding LocaleContext
989 */
990 protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
991 return new LocaleContext() {
992 public Locale getLocale() {
993 return localeResolver.resolveLocale(request);
994 }
995 public String toString() {
996 return getLocale().toString();
997 }
998 };
999 }
1000
1001 /**
1002 * Convert the request into a multipart request, and make multipart resolver available.
1003 * If no multipart resolver is set, simply use the existing request.
1004 * @param request current HTTP request
1005 * @return the processed request (multipart wrapper if necessary)
1006 * @see MultipartResolver#resolveMultipart
1007 */
1008 protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
1009 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
1010 if (request instanceof MultipartHttpServletRequest) {
1011 logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
1012 "this typically results from an additional MultipartFilter in web.xml");
1013 }
1014 else {
1015 return this.multipartResolver.resolveMultipart(request);
1016 }
1017 }
1018 // If not returned before: return original request.
1019 return request;
1020 }
1021
1022 /**
1023 * Clean up any resources used by the given multipart request (if any).
1024 * @param request current HTTP request
1025 * @see MultipartResolver#cleanupMultipart
1026 */
1027 protected void cleanupMultipart(HttpServletRequest request) {
1028 if (request instanceof MultipartHttpServletRequest) {
1029 this.multipartResolver.cleanupMultipart((MultipartHttpServletRequest) request);
1030 }
1031 }
1032
1033 /**
1034 * Return the HandlerExecutionChain for this request.
1035 * Try all handler mappings in order.
1036 * @param request current HTTP request
1037 * @param cache whether to cache the HandlerExecutionChain in a request attribute
1038 * @return the HandlerExceutionChain, or <code>null</code> if no handler could be found
1039 */
1040 protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
1041 HandlerExecutionChain handler =
1042 (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
1043 if (handler != null) {
1044 if (!cache) {
1045 request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
1046 }
1047 return handler;
1048 }
1049
1050 Iterator it = this.handlerMappings.iterator();
1051 while (it.hasNext()) {
1052 HandlerMapping hm = (HandlerMapping) it.next();
1053 if (logger.isTraceEnabled()) {
1054 logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" +
1055 getServletName() + "'");
1056 }
1057 handler = hm.getHandler(request);
1058 if (handler != null) {
1059 if (cache) {
1060 request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
1061 }
1062 return handler;
1063 }
1064 }
1065 return null;
1066 }
1067
1068 /**
1069 * No handler found -> set appropriate HTTP response status.
1070 * @param request current HTTP request
1071 * @param response current HTTP response
1072 * @throws Exception if preparing the response failed
1073 */
1074 protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
1075 if (pageNotFoundLogger.isWarnEnabled()) {
1076 String requestUri = new UrlPathHelper().getRequestUri(request);
1077 pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" +
1078 requestUri + "] in DispatcherServlet with name '" + getServletName() + "'");
1079 }
1080 response.sendError(HttpServletResponse.SC_NOT_FOUND);
1081 }
1082
1083 /**
1084 * Return the HandlerAdapter for this handler object.
1085 * @param handler the handler object to find an adapter for
1086 * @throws ServletException if no HandlerAdapter can be found for the handler.
1087 * This is a fatal error.
1088 */
1089 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
1090 Iterator it = this.handlerAdapters.iterator();
1091 while (it.hasNext()) {
1092 HandlerAdapter ha = (HandlerAdapter) it.next();
1093 if (logger.isTraceEnabled()) {
1094 logger.trace("Testing handler adapter [" + ha + "]");
1095 }
1096 if (ha.supports(handler)) {
1097 return ha;
1098 }
1099 }
1100 throw new ServletException("No adapter for handler [" + handler +
1101 "]: Does your handler implement a supported interface like Controller?");
1102 }
1103
1104 /**
1105 * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
1106 * @param request current HTTP request
1107 * @param response current HTTP response
1108 * @param handler the executed handler, or <code>null</code> if none chosen at the time of
1109 * the exception (for example, if multipart resolution failed)
1110 * @param ex the exception that got thrown during handler execution
1111 * @return a corresponding ModelAndView to forward to
1112 * @throws Exception if no error ModelAndView found
1113 */
1114 protected ModelAndView processHandlerException(
1115 HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
1116 throws Exception {
1117
1118 // Check registerer HandlerExceptionResolvers...
1119 ModelAndView exMv = null;
1120 for (Iterator it = this.handlerExceptionResolvers.iterator(); exMv == null && it.hasNext();) {
1121 HandlerExceptionResolver resolver = (HandlerExceptionResolver) it.next();
1122 exMv = resolver.resolveException(request, response, handler, ex);
1123 }
1124 if (exMv != null) {
1125 if (logger.isDebugEnabled()) {
1126 logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
1127 }
1128 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
1129 return exMv;
1130 }
1131
1132 // Send default responses for well-known exceptions, if possible.
1133 if (ex instanceof HttpRequestMethodNotSupportedException && !response.isCommitted()) {
1134 String[] supportedMethods = ((HttpRequestMethodNotSupportedException) ex).getSupportedMethods();
1135 if (supportedMethods != null) {
1136 response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
1137 }
1138 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
1139 return null;
1140 }
1141
1142 throw ex;
1143 }
1144
1145 /**
1146 * Render the given ModelAndView. This is the last stage in handling a request.
1147 * It may involve resolving the view by name.
1148 * @param mv the ModelAndView to render
1149 * @param request current HTTP servlet request
1150 * @param response current HTTP servlet response
1151 * @throws Exception if there's a problem rendering the view
1152 */
1153 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
1154 throws Exception {
1155
1156 // Determine locale for request and apply it to the response.
1157 Locale locale = this.localeResolver.resolveLocale(request);
1158 response.setLocale(locale);
1159
1160 View view = null;
1161
1162 if (mv.isReference()) {
1163 // We need to resolve the view name.
1164 view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
1165 if (view == null) {
1166 throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
1167 "' in servlet with name '" + getServletName() + "'");
1168 }
1169 }
1170 else {
1171 // No need to lookup: the ModelAndView object contains the actual View object.
1172 view = mv.getView();
1173 if (view == null) {
1174 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
1175 "View object in servlet with name '" + getServletName() + "'");
1176 }
1177 }
1178
1179 // Delegate to the View object for rendering.
1180 if (logger.isDebugEnabled()) {
1181 logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
1182 }
1183 view.render(mv.getModelInternal(), request, response);
1184 }
1185
1186 /**
1187 * Translate the supplied request into a default view name.
1188 * @param request current HTTP servlet request
1189 * @return the view name (or <code>null</code> if no default found)
1190 * @throws Exception if view name translation failed
1191 */
1192 protected String getDefaultViewName(HttpServletRequest request) throws Exception {
1193 return this.viewNameTranslator.getViewName(request);
1194 }
1195
1196 /**
1197 * Resolve the given view name into a View object (to be rendered).
1198 * <p>Default implementations asks all ViewResolvers of this dispatcher.
1199 * Can be overridden for custom resolution strategies, potentially based
1200 * on specific model attributes or request parameters.
1201 * @param viewName the name of the view to resolve
1202 * @param model the model to be passed to the view
1203 * @param locale the current locale
1204 * @param request current HTTP servlet request
1205 * @return the View object, or <code>null</code> if none found
1206 * @throws Exception if the view cannot be resolved
1207 * (typically in case of problems creating an actual View object)
1208 * @see ViewResolver#resolveViewName
1209 */
1210 protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request)
1211 throws Exception {
1212
1213 for (Iterator it = this.viewResolvers.iterator(); it.hasNext();) {
1214 ViewResolver viewResolver = (ViewResolver) it.next();
1215 View view = viewResolver.resolveViewName(viewName, locale);
1216 if (view != null) {
1217 return view;
1218 }
1219 }
1220 return null;
1221 }
1222
1223 /**
1224 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
1225 * Will just invoke afterCompletion for all interceptors whose preHandle
1226 * invocation has successfully completed and returned true.
1227 * @param mappedHandler the mapped HandlerExecutionChain
1228 * @param interceptorIndex index of last interceptor that successfully completed
1229 * @param ex Exception thrown on handler execution, or <code>null</code> if none
1230 * @see HandlerInterceptor#afterCompletion
1231 */
1232 private void triggerAfterCompletion(
1233 HandlerExecutionChain mappedHandler, int interceptorIndex,
1234 HttpServletRequest request, HttpServletResponse response, Exception ex)
1235 throws Exception {
1236
1237 // Apply afterCompletion methods of registered interceptors.
1238 if (mappedHandler != null) {
1239 HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
1240 if (interceptors != null) {
1241 for (int i = interceptorIndex; i >= 0; i--) {
1242 HandlerInterceptor interceptor = interceptors[i];
1243 try {
1244 interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
1245 }
1246 catch (Throwable ex2) {
1247 logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
1248 }
1249 }
1250 }
1251 }
1252 }
1253
1254 /**
1255 * Restore the request attributes after an include.
1256 * @param request current HTTP request
1257 * @param attributesSnapshot the snapshot of the request attributes
1258 * before the include
1259 */
1260 private void restoreAttributesAfterInclude(HttpServletRequest request, Map attributesSnapshot) {
1261 logger.debug("Restoring snapshot of request attributes after include");
1262
1263 // Need to copy into separate Collection here, to avoid side effects
1264 // on the Enumeration when removing attributes.
1265 Set attrsToCheck = new HashSet();
1266 Enumeration attrNames = request.getAttributeNames();
1267 while (attrNames.hasMoreElements()) {
1268 String attrName = (String) attrNames.nextElement();
1269 if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
1270 attrsToCheck.add(attrName);
1271 }
1272 }
1273
1274 // Iterate over the attributes to check, restoring the original value
1275 // or removing the attribute, respectively, if appropriate.
1276 for (Iterator it = attrsToCheck.iterator(); it.hasNext();) {
1277 String attrName = (String) it.next();
1278 Object attrValue = attributesSnapshot.get(attrName);
1279 if (attrValue != null) {
1280 if (logger.isDebugEnabled()) {
1281 logger.debug("Restoring original value of attribute [" + attrName + "] after include");
1282 }
1283 request.setAttribute(attrName, attrValue);
1284 }
1285 else {
1286 if (logger.isDebugEnabled()) {
1287 logger.debug("Removing attribute [" + attrName + "] after include");
1288 }
1289 request.removeAttribute(attrName);
1290 }
1291 }
1292 }
1293
1294 }