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