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}