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