View Javadoc
1   /*
2    * Copyright 2002-2014 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.support;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.TimeZone;
24  import javax.servlet.ServletContext;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  import javax.servlet.http.HttpSession;
28  import javax.servlet.jsp.jstl.core.Config;
29  
30  import org.springframework.context.MessageSource;
31  import org.springframework.context.MessageSourceResolvable;
32  import org.springframework.context.NoSuchMessageException;
33  import org.springframework.context.i18n.LocaleContext;
34  import org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext;
35  import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
36  import org.springframework.ui.context.Theme;
37  import org.springframework.ui.context.ThemeSource;
38  import org.springframework.ui.context.support.ResourceBundleThemeSource;
39  import org.springframework.util.Assert;
40  import org.springframework.util.ClassUtils;
41  import org.springframework.util.StringUtils;
42  import org.springframework.validation.BindException;
43  import org.springframework.validation.BindingResult;
44  import org.springframework.validation.Errors;
45  import org.springframework.web.bind.EscapedErrors;
46  import org.springframework.web.context.WebApplicationContext;
47  import org.springframework.web.servlet.LocaleContextResolver;
48  import org.springframework.web.servlet.LocaleResolver;
49  import org.springframework.web.servlet.ThemeResolver;
50  import org.springframework.web.util.HtmlUtils;
51  import org.springframework.web.util.UriTemplate;
52  import org.springframework.web.util.UrlPathHelper;
53  import org.springframework.web.util.WebUtils;
54  
55  /**
56   * Context holder for request-specific state, like current web application context, current locale, current theme,
57   * and potential binding errors. Provides easy access to localized messages and Errors instances.
58   *
59   * <p>Suitable for exposition to views, and usage within JSP's "useBean" tag, JSP scriptlets, JSTL EL, Velocity
60   * templates, etc. Necessary for views that do not have access to the servlet request, like Velocity templates.
61   *
62   * <p>Can be instantiated manually, or automatically exposed to views as model attribute via AbstractView's
63   * "requestContextAttribute" property.
64   *
65   * <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using
66   * an appropriate fallback for the locale (the HttpServletRequest's primary locale).
67   *
68   * @author Juergen Hoeller
69   * @author Rossen Stoyanchev
70   * @since 03.03.2003
71   * @see org.springframework.web.servlet.DispatcherServlet
72   * @see org.springframework.web.servlet.view.AbstractView#setRequestContextAttribute
73   * @see org.springframework.web.servlet.view.UrlBasedViewResolver#setRequestContextAttribute
74   * @see #getFallbackLocale()
75   */
76  public class RequestContext {
77  
78  	/**
79  	 * Default theme name used if the RequestContext cannot find a ThemeResolver.
80  	 * Only applies to non-DispatcherServlet requests.
81  	 * <p>Same as AbstractThemeResolver's default, but not linked in here to avoid package interdependencies.
82  	 * @see org.springframework.web.servlet.theme.AbstractThemeResolver#ORIGINAL_DEFAULT_THEME_NAME
83  	 */
84  	public static final String DEFAULT_THEME_NAME = "theme";
85  
86  	/**
87  	 * Request attribute to hold the current web application context for RequestContext usage.
88  	 * By default, the DispatcherServlet's context (or the root context as fallback) is exposed.
89  	 */
90  	public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = RequestContext.class.getName() + ".CONTEXT";
91  
92  	/**
93  	 * The name of the bean to use to look up in an implementation of
94  	 * {@link RequestDataValueProcessor} has been configured.
95  	 */
96  	private static final String REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME = "requestDataValueProcessor";
97  
98  
99  	protected static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.core.Config",
100 			RequestContext.class.getClassLoader());
101 
102 	private HttpServletRequest request;
103 
104 	private HttpServletResponse response;
105 
106 	private Map<String, Object> model;
107 
108 	private WebApplicationContext webApplicationContext;
109 
110 	private Locale locale;
111 
112 	private TimeZone timeZone;
113 
114 	private Theme theme;
115 
116 	private Boolean defaultHtmlEscape;
117 
118 	private Boolean responseEncodedHtmlEscape;
119 
120 	private UrlPathHelper urlPathHelper;
121 
122 	private RequestDataValueProcessor requestDataValueProcessor;
123 
124 	private Map<String, Errors> errorsMap;
125 
126 
127 	/**
128 	 * Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
129 	 * <p>This only works with InternalResourceViews, as Errors instances are part of the model and not
130 	 * normally exposed as request attributes. It will typically be used within JSPs or custom tags.
131 	 * <p><b>Will only work within a DispatcherServlet request.</b>
132 	 * Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
133 	 * @param request current HTTP request
134 	 * @see org.springframework.web.servlet.DispatcherServlet
135 	 * @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.ServletContext)
136 	 */
137 	public RequestContext(HttpServletRequest request) {
138 		initContext(request, null, null, null);
139 	}
140 
141 	/**
142 	 * Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
143 	 * <p>This only works with InternalResourceViews, as Errors instances are part of the model and not
144 	 * normally exposed as request attributes. It will typically be used within JSPs or custom tags.
145 	 * <p><b>Will only work within a DispatcherServlet request.</b>
146 	 * Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
147 	 * @param request current HTTP request
148 	 * @param response current HTTP response
149 	 * @see org.springframework.web.servlet.DispatcherServlet
150 	 * @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext, Map)
151 	 */
152 	public RequestContext(HttpServletRequest request, HttpServletResponse response) {
153 		initContext(request, response, null, null);
154 	}
155 
156 	/**
157 	 * Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
158 	 * <p>This only works with InternalResourceViews, as Errors instances are part of the model and not
159 	 * normally exposed as request attributes. It will typically be used within JSPs or custom tags.
160 	 * <p>If a ServletContext is specified, the RequestContext will also work with the root
161 	 * WebApplicationContext (outside a DispatcherServlet).
162 	 * @param request current HTTP request
163 	 * @param servletContext the servlet context of the web application (can be {@code null};
164 	 * necessary for fallback to root WebApplicationContext)
165 	 * @see org.springframework.web.context.WebApplicationContext
166 	 * @see org.springframework.web.servlet.DispatcherServlet
167 	 */
168 	public RequestContext(HttpServletRequest request, ServletContext servletContext) {
169 		initContext(request, null, servletContext, null);
170 	}
171 
172 	/**
173 	 * Create a new RequestContext for the given request, using the given model attributes for Errors retrieval.
174 	 * <p>This works with all View implementations. It will typically be used by View implementations.
175 	 * <p><b>Will only work within a DispatcherServlet request.</b>
176 	 * Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
177 	 * @param request current HTTP request
178 	 * @param model the model attributes for the current view (can be {@code null},
179 	 * using the request attributes for Errors retrieval)
180 	 * @see org.springframework.web.servlet.DispatcherServlet
181 	 * @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext, Map)
182 	 */
183 	public RequestContext(HttpServletRequest request, Map<String, Object> model) {
184 		initContext(request, null, null, model);
185 	}
186 
187 	/**
188 	 * Create a new RequestContext for the given request, using the given model attributes for Errors retrieval.
189 	 * <p>This works with all View implementations. It will typically be used by View implementations.
190 	 * <p>If a ServletContext is specified, the RequestContext will also work with a root
191 	 * WebApplicationContext (outside a DispatcherServlet).
192 	 * @param request current HTTP request
193 	 * @param response current HTTP response
194 	 * @param servletContext the servlet context of the web application (can be {@code null}; necessary for
195 	 * fallback to root WebApplicationContext)
196 	 * @param model the model attributes for the current view (can be {@code null}, using the request attributes
197 	 * for Errors retrieval)
198 	 * @see org.springframework.web.context.WebApplicationContext
199 	 * @see org.springframework.web.servlet.DispatcherServlet
200 	 */
201 	public RequestContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext,
202 			Map<String, Object> model) {
203 
204 		initContext(request, response, servletContext, model);
205 	}
206 
207 	/**
208 	 * Default constructor for subclasses.
209 	 */
210 	protected RequestContext() {
211 	}
212 
213 
214 	/**
215 	 * Initialize this context with the given request, using the given model attributes for Errors retrieval.
216 	 * <p>Delegates to {@code getFallbackLocale} and {@code getFallbackTheme} for determining the fallback
217 	 * locale and theme, respectively, if no LocaleResolver and/or ThemeResolver can be found in the request.
218 	 * @param request current HTTP request
219 	 * @param servletContext the servlet context of the web application (can be {@code null}; necessary for
220 	 * fallback to root WebApplicationContext)
221 	 * @param model the model attributes for the current view (can be {@code null}, using the request attributes
222 	 * for Errors retrieval)
223 	 * @see #getFallbackLocale
224 	 * @see #getFallbackTheme
225 	 * @see org.springframework.web.servlet.DispatcherServlet#LOCALE_RESOLVER_ATTRIBUTE
226 	 * @see org.springframework.web.servlet.DispatcherServlet#THEME_RESOLVER_ATTRIBUTE
227 	 */
228 	protected void initContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext,
229 			Map<String, Object> model) {
230 
231 		this.request = request;
232 		this.response = response;
233 		this.model = model;
234 
235 		// Fetch WebApplicationContext, either from DispatcherServlet or the root context.
236 		// ServletContext needs to be specified to be able to fall back to the root context!
237 		this.webApplicationContext = (WebApplicationContext) request.getAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE);
238 		if (this.webApplicationContext == null) {
239 			this.webApplicationContext = RequestContextUtils.getWebApplicationContext(request, servletContext);
240 		}
241 
242 		// Determine locale to use for this RequestContext.
243 		LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
244 		if (localeResolver instanceof LocaleContextResolver) {
245 			LocaleContext localeContext = ((LocaleContextResolver) localeResolver).resolveLocaleContext(request);
246 			this.locale = localeContext.getLocale();
247 			if (localeContext instanceof TimeZoneAwareLocaleContext) {
248 				this.timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
249 			}
250 		}
251 		else if (localeResolver != null) {
252 			// Try LocaleResolver (we're within a DispatcherServlet request).
253 			this.locale = localeResolver.resolveLocale(request);
254 		}
255 
256 		// Try JSTL fallbacks if necessary.
257 		if (this.locale == null) {
258 			this.locale = getFallbackLocale();
259 		}
260 		if (this.timeZone == null) {
261 			this.timeZone = getFallbackTimeZone();
262 		}
263 
264 		// Determine default HTML escape setting from the "defaultHtmlEscape"
265 		// context-param in web.xml, if any.
266 		this.defaultHtmlEscape = WebUtils.getDefaultHtmlEscape(this.webApplicationContext.getServletContext());
267 
268 		// Determine response-encoded HTML escape setting from the "responseEncodedHtmlEscape"
269 		// context-param in web.xml, if any.
270 		this.responseEncodedHtmlEscape = WebUtils.getResponseEncodedHtmlEscape(this.webApplicationContext.getServletContext());
271 
272 		this.urlPathHelper = new UrlPathHelper();
273 
274 		if (this.webApplicationContext.containsBean(REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) {
275 			this.requestDataValueProcessor = this.webApplicationContext.getBean(
276 					REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, RequestDataValueProcessor.class);
277 		}
278 	}
279 
280 	/**
281 	 * Determine the fallback locale for this context.
282 	 * <p>The default implementation checks for a JSTL locale attribute in request, session
283 	 * or application scope; if not found, returns the {@code HttpServletRequest.getLocale()}.
284 	 * @return the fallback locale (never {@code null})
285 	 * @see javax.servlet.http.HttpServletRequest#getLocale()
286 	 */
287 	protected Locale getFallbackLocale() {
288 		if (jstlPresent) {
289 			Locale locale = JstlLocaleResolver.getJstlLocale(getRequest(), getServletContext());
290 			if (locale != null) {
291 				return locale;
292 			}
293 		}
294 		return getRequest().getLocale();
295 	}
296 
297 	/**
298 	 * Determine the fallback time zone for this context.
299 	 * <p>The default implementation checks for a JSTL time zone attribute in request,
300 	 * session or application scope; returns {@code null} if not found.
301 	 * @return the fallback time zone (or {@code null} if none derivable from the request)
302 	 */
303 	protected TimeZone getFallbackTimeZone() {
304 		if (jstlPresent) {
305 			TimeZone timeZone = JstlLocaleResolver.getJstlTimeZone(getRequest(), getServletContext());
306 			if (timeZone != null) {
307 				return timeZone;
308 			}
309 		}
310 		return null;
311 	}
312 
313 	/**
314 	 * Determine the fallback theme for this context.
315 	 * <p>The default implementation returns the default theme (with name "theme").
316 	 * @return the fallback theme (never {@code null})
317 	 */
318 	protected Theme getFallbackTheme() {
319 		ThemeSource themeSource = RequestContextUtils.getThemeSource(getRequest());
320 		if (themeSource == null) {
321 			themeSource = new ResourceBundleThemeSource();
322 		}
323 		Theme theme = themeSource.getTheme(DEFAULT_THEME_NAME);
324 		if (theme == null) {
325 			throw new IllegalStateException("No theme defined and no fallback theme found");
326 		}
327 		return theme;
328 	}
329 
330 
331 	/**
332 	 * Return the underlying HttpServletRequest. Only intended for cooperating classes in this package.
333 	 */
334 	protected final HttpServletRequest getRequest() {
335 		return this.request;
336 	}
337 
338 	/**
339 	 * Return the underlying ServletContext. Only intended for cooperating classes in this package.
340 	 */
341 	protected final ServletContext getServletContext() {
342 		return this.webApplicationContext.getServletContext();
343 	}
344 
345 	/**
346 	 * Return the current WebApplicationContext.
347 	 */
348 	public final WebApplicationContext getWebApplicationContext() {
349 		return this.webApplicationContext;
350 	}
351 
352 	/**
353 	 * Return the current WebApplicationContext as MessageSource.
354 	 */
355 	public final MessageSource getMessageSource() {
356 		return this.webApplicationContext;
357 	}
358 
359 	/**
360 	 * Return the model Map that this RequestContext encapsulates, if any.
361 	 * @return the populated model Map, or {@code null} if none available
362 	 */
363 	public final Map<String, Object> getModel() {
364 		return this.model;
365 	}
366 
367 	/**
368 	 * Return the current Locale (falling back to the request locale; never {@code null}).
369 	 * <p>Typically coming from a DispatcherServlet's {@link LocaleResolver}.
370 	 * Also includes a fallback check for JSTL's Locale attribute.
371 	 * @see RequestContextUtils#getLocale
372 	 */
373 	public final Locale getLocale() {
374 		return this.locale;
375 	}
376 
377 	/**
378 	 * Return the current TimeZone (or {@code null} if none derivable from the request).
379 	 * <p>Typically coming from a DispatcherServlet's {@link LocaleContextResolver}.
380 	 * Also includes a fallback check for JSTL's TimeZone attribute.
381 	 * @see RequestContextUtils#getTimeZone
382 	 */
383 	public TimeZone getTimeZone() {
384 		return this.timeZone;
385 	}
386 
387 	/**
388 	 * Change the current locale to the specified one,
389 	 * storing the new locale through the configured {@link LocaleResolver}.
390 	 * @param locale the new locale
391 	 * @see LocaleResolver#setLocale
392 	 * @see #changeLocale(java.util.Locale, java.util.TimeZone)
393 	 */
394 	public void changeLocale(Locale locale) {
395 		LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(this.request);
396 		if (localeResolver == null) {
397 			throw new IllegalStateException("Cannot change locale if no LocaleResolver configured");
398 		}
399 		localeResolver.setLocale(this.request, this.response, locale);
400 		this.locale = locale;
401 	}
402 
403 	/**
404 	 * Change the current locale to the specified locale and time zone context,
405 	 * storing the new locale context through the configured {@link LocaleResolver}.
406 	 * @param locale the new locale
407 	 * @param timeZone the new time zone
408 	 * @see LocaleContextResolver#setLocaleContext
409 	 * @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext
410 	 */
411 	public void changeLocale(Locale locale, TimeZone timeZone) {
412 		LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(this.request);
413 		if (!(localeResolver instanceof LocaleContextResolver)) {
414 			throw new IllegalStateException("Cannot change locale context if no LocaleContextResolver configured");
415 		}
416 		((LocaleContextResolver) localeResolver).setLocaleContext(this.request, this.response,
417 				new SimpleTimeZoneAwareLocaleContext(locale, timeZone));
418 		this.locale = locale;
419 		this.timeZone = timeZone;
420 	}
421 
422 	/**
423 	 * Return the current theme (never {@code null}).
424 	 * <p>Resolved lazily for more efficiency when theme support is not being used.
425 	 */
426 	public Theme getTheme() {
427 		if (this.theme == null) {
428 			// Lazily determine theme to use for this RequestContext.
429 			this.theme = RequestContextUtils.getTheme(this.request);
430 			if (this.theme == null) {
431 				// No ThemeResolver and ThemeSource available -> try fallback.
432 				this.theme = getFallbackTheme();
433 			}
434 		}
435 		return this.theme;
436 	}
437 
438 	/**
439 	 * Change the current theme to the specified one,
440 	 * storing the new theme name through the configured {@link ThemeResolver}.
441 	 * @param theme the new theme
442 	 * @see ThemeResolver#setThemeName
443 	 */
444 	public void changeTheme(Theme theme) {
445 		ThemeResolver themeResolver = RequestContextUtils.getThemeResolver(this.request);
446 		if (themeResolver == null) {
447 			throw new IllegalStateException("Cannot change theme if no ThemeResolver configured");
448 		}
449 		themeResolver.setThemeName(this.request, this.response, (theme != null ? theme.getName() : null));
450 		this.theme = theme;
451 	}
452 
453 	/**
454 	 * Change the current theme to the specified theme by name,
455 	 * storing the new theme name through the configured {@link ThemeResolver}.
456 	 * @param themeName the name of the new theme
457 	 * @see ThemeResolver#setThemeName
458 	 */
459 	public void changeTheme(String themeName) {
460 		ThemeResolver themeResolver = RequestContextUtils.getThemeResolver(this.request);
461 		if (themeResolver == null) {
462 			throw new IllegalStateException("Cannot change theme if no ThemeResolver configured");
463 		}
464 		themeResolver.setThemeName(this.request, this.response, themeName);
465 		// Ask for re-resolution on next getTheme call.
466 		this.theme = null;
467 	}
468 
469 	/**
470 	 * (De)activate default HTML escaping for messages and errors, for the scope of this RequestContext.
471 	 * <p>The default is the application-wide setting (the "defaultHtmlEscape" context-param in web.xml).
472 	 * @see org.springframework.web.util.WebUtils#isDefaultHtmlEscape
473 	 */
474 	public void setDefaultHtmlEscape(boolean defaultHtmlEscape) {
475 		this.defaultHtmlEscape = defaultHtmlEscape;
476 	}
477 
478 	/**
479 	 * Is default HTML escaping active? Falls back to {@code false} in case of no explicit default given.
480 	 */
481 	public boolean isDefaultHtmlEscape() {
482 		return (this.defaultHtmlEscape != null && this.defaultHtmlEscape.booleanValue());
483 	}
484 
485 	/**
486 	 * Return the default HTML escape setting, differentiating between no default specified and an explicit value.
487 	 * @return whether default HTML escaping is enabled (null = no explicit default)
488 	 */
489 	public Boolean getDefaultHtmlEscape() {
490 		return this.defaultHtmlEscape;
491 	}
492 
493 	/**
494 	 * Is HTML escaping using the response encoding by default?
495 	 * If enabled, only XML markup significant characters will be escaped with UTF-* encodings.
496 	 * <p>Falls back to {@code false} in case of no explicit default given.
497 	 * @since 4.1.2
498 	 */
499 	public boolean isResponseEncodedHtmlEscape() {
500 		return (this.responseEncodedHtmlEscape != null && this.responseEncodedHtmlEscape.booleanValue());
501 	}
502 
503 	/**
504 	 * Return the default setting about use of response encoding for HTML escape setting,
505 	 * differentiating between no default specified and an explicit value.
506 	 * @return whether default use of response encoding HTML escaping is enabled (null = no explicit default)
507 	 * @since 4.1.2
508 	 */
509 	public Boolean getResponseEncodedHtmlEscape() {
510 		return this.responseEncodedHtmlEscape;
511 	}
512 
513 
514 	/**
515 	 * Set the UrlPathHelper to use for context path and request URI decoding.
516 	 * Can be used to pass a shared UrlPathHelper instance in.
517 	 * <p>A default UrlPathHelper is always available.
518 	 */
519 	public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
520 		Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
521 		this.urlPathHelper = urlPathHelper;
522 	}
523 
524 	/**
525 	 * Return the UrlPathHelper used for context path and request URI decoding.
526 	 * Can be used to configure the current UrlPathHelper.
527 	 * <p>A default UrlPathHelper is always available.
528 	 */
529 	public UrlPathHelper getUrlPathHelper() {
530 		return this.urlPathHelper;
531 	}
532 
533 	/**
534 	 * Return the RequestDataValueProcessor instance to use obtained from the
535 	 * WebApplicationContext under the name {@code "requestDataValueProcessor"}.
536 	 * Or {@code null} if no matching bean was found.
537 	 */
538 	public RequestDataValueProcessor getRequestDataValueProcessor() {
539 		return this.requestDataValueProcessor;
540 	}
541 
542 	/**
543 	 * Return the context path of the original request, that is, the path that
544 	 * indicates the current web application. This is useful for building links
545 	 * to other resources within the application.
546 	 * <p>Delegates to the UrlPathHelper for decoding.
547 	 * @see javax.servlet.http.HttpServletRequest#getContextPath
548 	 * @see #getUrlPathHelper
549 	 */
550 	public String getContextPath() {
551 		return this.urlPathHelper.getOriginatingContextPath(this.request);
552 	}
553 
554 	/**
555 	 * Return a context-aware URl for the given relative URL.
556 	 * @param relativeUrl the relative URL part
557 	 * @return a URL that points back to the server with an absolute path (also URL-encoded accordingly)
558 	 */
559 	public String getContextUrl(String relativeUrl) {
560 		String url = getContextPath() + relativeUrl;
561 		if (this.response != null) {
562 			url = this.response.encodeURL(url);
563 		}
564 		return url;
565 	}
566 
567 	/**
568 	 * Return a context-aware URl for the given relative URL with placeholders (named keys with braces {@code {}}).
569 	 * For example, send in a relative URL {@code foo/{bar}?spam={spam}} and a parameter map
570 	 * {@code {bar=baz,spam=nuts}} and the result will be {@code [contextpath]/foo/baz?spam=nuts}.
571 	 * @param relativeUrl the relative URL part
572 	 * @param params a map of parameters to insert as placeholders in the url
573 	 * @return a URL that points back to the server with an absolute path (also URL-encoded accordingly)
574 	 */
575 	public String getContextUrl(String relativeUrl, Map<String, ?> params) {
576 		String url = getContextPath() + relativeUrl;
577 		UriTemplate template = new UriTemplate(url);
578 		url = template.expand(params).toASCIIString();
579 		if (this.response != null) {
580 			url = this.response.encodeURL(url);
581 		}
582 		return url;
583 	}
584 
585 	/**
586 	 * Return the path to URL mappings within the current servlet including the
587 	 * context path and the servlet path of the original request. This is useful
588 	 * for building links to other resources within the application where a
589 	 * servlet mapping of the style {@code "/main/*"} is used.
590 	 * <p>Delegates to the UrlPathHelper to determine the context and servlet path.
591 	 */
592 	public String getPathToServlet() {
593 		String path = this.urlPathHelper.getOriginatingContextPath(this.request);
594 		if (StringUtils.hasText(this.urlPathHelper.getPathWithinServletMapping(this.request))) {
595 			path += this.urlPathHelper.getOriginatingServletPath(this.request);
596 		}
597 		return path;
598 	}
599 
600 	/**
601 	 * Return the request URI of the original request, that is, the invoked URL
602 	 * without parameters. This is particularly useful as HTML form action target,
603 	 * possibly in combination with the original query string.
604 	 * <p>Delegates to the UrlPathHelper for decoding.
605 	 * @see #getQueryString
606 	 * @see org.springframework.web.util.UrlPathHelper#getOriginatingRequestUri
607 	 * @see #getUrlPathHelper
608 	 */
609 	public String getRequestUri() {
610 		return this.urlPathHelper.getOriginatingRequestUri(this.request);
611 	}
612 
613 	/**
614 	 * Return the query string of the current request, that is, the part after
615 	 * the request path. This is particularly useful for building an HTML form
616 	 * action target in combination with the original request URI.
617 	 * <p>Delegates to the UrlPathHelper for decoding.
618 	 * @see #getRequestUri
619 	 * @see org.springframework.web.util.UrlPathHelper#getOriginatingQueryString
620 	 * @see #getUrlPathHelper
621 	 */
622 	public String getQueryString() {
623 		return this.urlPathHelper.getOriginatingQueryString(this.request);
624 	}
625 
626 	/**
627 	 * Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
628 	 * @param code code of the message
629 	 * @param defaultMessage String to return if the lookup fails
630 	 * @return the message
631 	 */
632 	public String getMessage(String code, String defaultMessage) {
633 		return getMessage(code, null, defaultMessage, isDefaultHtmlEscape());
634 	}
635 
636 	/**
637 	 * Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
638 	 * @param code code of the message
639 	 * @param args arguments for the message, or {@code null} if none
640 	 * @param defaultMessage String to return if the lookup fails
641 	 * @return the message
642 	 */
643 	public String getMessage(String code, Object[] args, String defaultMessage) {
644 		return getMessage(code, args, defaultMessage, isDefaultHtmlEscape());
645 	}
646 
647 	/**
648 	 * Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
649 	 * @param code code of the message
650 	 * @param args arguments for the message as a List, or {@code null} if none
651 	 * @param defaultMessage String to return if the lookup fails
652 	 * @return the message
653 	 */
654 	public String getMessage(String code, List<?> args, String defaultMessage) {
655 		return getMessage(code, (args != null ? args.toArray() : null), defaultMessage, isDefaultHtmlEscape());
656 	}
657 
658 	/**
659 	 * Retrieve the message for the given code.
660 	 * @param code code of the message
661 	 * @param args arguments for the message, or {@code null} if none
662 	 * @param defaultMessage String to return if the lookup fails
663 	 * @param htmlEscape HTML escape the message?
664 	 * @return the message
665 	 */
666 	public String getMessage(String code, Object[] args, String defaultMessage, boolean htmlEscape) {
667 		String msg = this.webApplicationContext.getMessage(code, args, defaultMessage, this.locale);
668 		return (htmlEscape ? HtmlUtils.htmlEscape(msg) : msg);
669 	}
670 
671 	/**
672 	 * Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
673 	 * @param code code of the message
674 	 * @return the message
675 	 * @throws org.springframework.context.NoSuchMessageException if not found
676 	 */
677 	public String getMessage(String code) throws NoSuchMessageException {
678 		return getMessage(code, null, isDefaultHtmlEscape());
679 	}
680 
681 	/**
682 	 * Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
683 	 * @param code code of the message
684 	 * @param args arguments for the message, or {@code null} if none
685 	 * @return the message
686 	 * @throws org.springframework.context.NoSuchMessageException if not found
687 	 */
688 	public String getMessage(String code, Object[] args) throws NoSuchMessageException {
689 		return getMessage(code, args, isDefaultHtmlEscape());
690 	}
691 
692 	/**
693 	 * Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
694 	 * @param code code of the message
695 	 * @param args arguments for the message as a List, or {@code null} if none
696 	 * @return the message
697 	 * @throws org.springframework.context.NoSuchMessageException if not found
698 	 */
699 	public String getMessage(String code, List<?> args) throws NoSuchMessageException {
700 		return getMessage(code, (args != null ? args.toArray() : null), isDefaultHtmlEscape());
701 	}
702 
703 	/**
704 	 * Retrieve the message for the given code.
705 	 * @param code code of the message
706 	 * @param args arguments for the message, or {@code null} if none
707 	 * @param htmlEscape HTML escape the message?
708 	 * @return the message
709 	 * @throws org.springframework.context.NoSuchMessageException if not found
710 	 */
711 	public String getMessage(String code, Object[] args, boolean htmlEscape) throws NoSuchMessageException {
712 		String msg = this.webApplicationContext.getMessage(code, args, this.locale);
713 		return (htmlEscape ? HtmlUtils.htmlEscape(msg) : msg);
714 	}
715 
716 	/**
717 	 * Retrieve the given MessageSourceResolvable (e.g. an ObjectError instance), using the "defaultHtmlEscape" setting.
718 	 * @param resolvable the MessageSourceResolvable
719 	 * @return the message
720 	 * @throws org.springframework.context.NoSuchMessageException if not found
721 	 */
722 	public String getMessage(MessageSourceResolvable resolvable) throws NoSuchMessageException {
723 		return getMessage(resolvable, isDefaultHtmlEscape());
724 	}
725 
726 	/**
727 	 * Retrieve the given MessageSourceResolvable (e.g. an ObjectError instance).
728 	 * @param resolvable the MessageSourceResolvable
729 	 * @param htmlEscape HTML escape the message?
730 	 * @return the message
731 	 * @throws org.springframework.context.NoSuchMessageException if not found
732 	 */
733 	public String getMessage(MessageSourceResolvable resolvable, boolean htmlEscape) throws NoSuchMessageException {
734 		String msg = this.webApplicationContext.getMessage(resolvable, this.locale);
735 		return (htmlEscape ? HtmlUtils.htmlEscape(msg) : msg);
736 	}
737 
738 	/**
739 	 * Retrieve the theme message for the given code.
740 	 * <p>Note that theme messages are never HTML-escaped, as they typically denote
741 	 * theme-specific resource paths and not client-visible messages.
742 	 * @param code code of the message
743 	 * @param defaultMessage String to return if the lookup fails
744 	 * @return the message
745 	 */
746 	public String getThemeMessage(String code, String defaultMessage) {
747 		return getTheme().getMessageSource().getMessage(code, null, defaultMessage, this.locale);
748 	}
749 
750 	/**
751 	 * Retrieve the theme message for the given code.
752 	 * <p>Note that theme messages are never HTML-escaped, as they typically denote
753 	 * theme-specific resource paths and not client-visible messages.
754 	 * @param code code of the message
755 	 * @param args arguments for the message, or {@code null} if none
756 	 * @param defaultMessage String to return if the lookup fails
757 	 * @return the message
758 	 */
759 	public String getThemeMessage(String code, Object[] args, String defaultMessage) {
760 		return getTheme().getMessageSource().getMessage(code, args, defaultMessage, this.locale);
761 	}
762 
763 	/**
764 	 * Retrieve the theme message for the given code.
765 	 * <p>Note that theme messages are never HTML-escaped, as they typically denote
766 	 * theme-specific resource paths and not client-visible messages.
767 	 * @param code code of the message
768 	 * @param args arguments for the message as a List, or {@code null} if none
769 	 * @param defaultMessage String to return if the lookup fails
770 	 * @return the message
771 	 */
772 	public String getThemeMessage(String code, List<?> args, String defaultMessage) {
773 		return getTheme().getMessageSource().getMessage(code, (args != null ? args.toArray() : null), defaultMessage,
774 				this.locale);
775 	}
776 
777 	/**
778 	 * Retrieve the theme message for the given code.
779 	 * <p>Note that theme messages are never HTML-escaped, as they typically denote
780 	 * theme-specific resource paths and not client-visible messages.
781 	 * @param code code of the message
782 	 * @return the message
783 	 * @throws org.springframework.context.NoSuchMessageException if not found
784 	 */
785 	public String getThemeMessage(String code) throws NoSuchMessageException {
786 		return getTheme().getMessageSource().getMessage(code, null, this.locale);
787 	}
788 
789 	/**
790 	 * Retrieve the theme message for the given code.
791 	 * <p>Note that theme messages are never HTML-escaped, as they typically denote
792 	 * theme-specific resource paths and not client-visible messages.
793 	 * @param code code of the message
794 	 * @param args arguments for the message, or {@code null} if none
795 	 * @return the message
796 	 * @throws org.springframework.context.NoSuchMessageException if not found
797 	 */
798 	public String getThemeMessage(String code, Object[] args) throws NoSuchMessageException {
799 		return getTheme().getMessageSource().getMessage(code, args, this.locale);
800 	}
801 
802 	/**
803 	 * Retrieve the theme message for the given code.
804 	 * <p>Note that theme messages are never HTML-escaped, as they typically denote
805 	 * theme-specific resource paths and not client-visible messages.
806 	 * @param code code of the message
807 	 * @param args arguments for the message as a List, or {@code null} if none
808 	 * @return the message
809 	 * @throws org.springframework.context.NoSuchMessageException if not found
810 	 */
811 	public String getThemeMessage(String code, List<?> args) throws NoSuchMessageException {
812 		return getTheme().getMessageSource().getMessage(code, (args != null ? args.toArray() : null), this.locale);
813 	}
814 
815 	/**
816 	 * Retrieve the given MessageSourceResolvable in the current theme.
817 	 * <p>Note that theme messages are never HTML-escaped, as they typically denote
818 	 * theme-specific resource paths and not client-visible messages.
819 	 * @param resolvable the MessageSourceResolvable
820 	 * @return the message
821 	 * @throws org.springframework.context.NoSuchMessageException if not found
822 	 */
823 	public String getThemeMessage(MessageSourceResolvable resolvable) throws NoSuchMessageException {
824 		return getTheme().getMessageSource().getMessage(resolvable, this.locale);
825 	}
826 
827 	/**
828 	 * Retrieve the Errors instance for the given bind object, using the "defaultHtmlEscape" setting.
829 	 * @param name name of the bind object
830 	 * @return the Errors instance, or {@code null} if not found
831 	 */
832 	public Errors getErrors(String name) {
833 		return getErrors(name, isDefaultHtmlEscape());
834 	}
835 
836 	/**
837 	 * Retrieve the Errors instance for the given bind object.
838 	 * @param name name of the bind object
839 	 * @param htmlEscape create an Errors instance with automatic HTML escaping?
840 	 * @return the Errors instance, or {@code null} if not found
841 	 */
842 	public Errors getErrors(String name, boolean htmlEscape) {
843 		if (this.errorsMap == null) {
844 			this.errorsMap = new HashMap<String, Errors>();
845 		}
846 		Errors errors = this.errorsMap.get(name);
847 		boolean put = false;
848 		if (errors == null) {
849 			errors = (Errors) getModelObject(BindingResult.MODEL_KEY_PREFIX + name);
850 			// Check old BindException prefix for backwards compatibility.
851 			if (errors instanceof BindException) {
852 				errors = ((BindException) errors).getBindingResult();
853 			}
854 			if (errors == null) {
855 				return null;
856 			}
857 			put = true;
858 		}
859 		if (htmlEscape && !(errors instanceof EscapedErrors)) {
860 			errors = new EscapedErrors(errors);
861 			put = true;
862 		}
863 		else if (!htmlEscape && errors instanceof EscapedErrors) {
864 			errors = ((EscapedErrors) errors).getSource();
865 			put = true;
866 		}
867 		if (put) {
868 			this.errorsMap.put(name, errors);
869 		}
870 		return errors;
871 	}
872 
873 	/**
874 	 * Retrieve the model object for the given model name, either from the model or from the request attributes.
875 	 * @param modelName the name of the model object
876 	 * @return the model object
877 	 */
878 	protected Object getModelObject(String modelName) {
879 		if (this.model != null) {
880 			return this.model.get(modelName);
881 		}
882 		else {
883 			return this.request.getAttribute(modelName);
884 		}
885 	}
886 
887 	/**
888 	 * Create a BindStatus for the given bind object, using the "defaultHtmlEscape" setting.
889 	 * @param path the bean and property path for which values and errors will be resolved (e.g. "person.age")
890 	 * @return the new BindStatus instance
891 	 * @throws IllegalStateException if no corresponding Errors object found
892 	 */
893 	public BindStatus getBindStatus(String path) throws IllegalStateException {
894 		return new BindStatus(this, path, isDefaultHtmlEscape());
895 	}
896 
897 	/**
898 	 * Create a BindStatus for the given bind object, using the "defaultHtmlEscape" setting.
899 	 * @param path the bean and property path for which values and errors will be resolved (e.g. "person.age")
900 	 * @param htmlEscape create a BindStatus with automatic HTML escaping?
901 	 * @return the new BindStatus instance
902 	 * @throws IllegalStateException if no corresponding Errors object found
903 	 */
904 	public BindStatus getBindStatus(String path, boolean htmlEscape) throws IllegalStateException {
905 		return new BindStatus(this, path, htmlEscape);
906 	}
907 
908 
909 	/**
910 	 * Inner class that isolates the JSTL dependency.
911 	 * Just called to resolve the fallback locale if the JSTL API is present.
912 	 */
913 	private static class JstlLocaleResolver {
914 
915 		public static Locale getJstlLocale(HttpServletRequest request, ServletContext servletContext) {
916 			Object localeObject = Config.get(request, Config.FMT_LOCALE);
917 			if (localeObject == null) {
918 				HttpSession session = request.getSession(false);
919 				if (session != null) {
920 					localeObject = Config.get(session, Config.FMT_LOCALE);
921 				}
922 				if (localeObject == null && servletContext != null) {
923 					localeObject = Config.get(servletContext, Config.FMT_LOCALE);
924 				}
925 			}
926 			return (localeObject instanceof Locale ? (Locale) localeObject : null);
927 		}
928 
929 		public static TimeZone getJstlTimeZone(HttpServletRequest request, ServletContext servletContext) {
930 			Object timeZoneObject = Config.get(request, Config.FMT_TIME_ZONE);
931 			if (timeZoneObject == null) {
932 				HttpSession session = request.getSession(false);
933 				if (session != null) {
934 					timeZoneObject = Config.get(session, Config.FMT_TIME_ZONE);
935 				}
936 				if (timeZoneObject == null && servletContext != null) {
937 					timeZoneObject = Config.get(servletContext, Config.FMT_TIME_ZONE);
938 				}
939 			}
940 			return (timeZoneObject instanceof TimeZone ? (TimeZone) timeZoneObject : null);
941 		}
942 	}
943 
944 }