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}