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