Comprehensive update to the framework's TimeZone handling, including a new TimeZoneAwareLocaleContext and a LocaleContextResolver for Spring MVC

A few noteworthy minor changes: LocaleContext.getLocale() may return null in special cases (not by default), which our own accessing classes are able to handle now. If there is a non-null TimeZone user setting, we're exposing it to all collaborating libraries, in particular to JSTL, Velocity and JasperReports. Our JSR-310 and Joda-Time support falls back to checking the general LocaleContext TimeZone now, adapting it to their time zone types, if no more specific setting has been provided. Our DefaultConversionService has TimeZone<->ZoneId converters registered. And finally, we're using a custom parseTimeZoneString method now that doesn't accept the TimeZone.getTimeZone(String) GMT fallback for an invalid time zone id anymore.

Issue: SPR-1528
This commit is contained in:
Juergen Hoeller
2013-10-04 23:14:08 +02:00
parent 52cca48f40
commit 4574528a27
35 changed files with 1570 additions and 270 deletions

View File

@@ -28,7 +28,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -36,6 +35,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -1042,16 +1042,17 @@ public class DispatcherServlet extends FrameworkServlet {
*/
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
return new LocaleContext() {
@Override
public Locale getLocale() {
return localeResolver.resolveLocale(request);
}
@Override
public String toString() {
return getLocale().toString();
}
};
if (this.localeResolver instanceof LocaleContextResolver) {
return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request);
}
else {
return new LocaleContext() {
@Override
public Locale getLocale() {
return localeResolver.resolveLocale(request);
}
};
}
}
/**

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContext;
/**
* Extension of {@link LocaleResolver}, adding support for a rich locale context
* (potentially including locale and time zone information).
*
* @author Juergen Hoeller
* @since 4.0
* @see org.springframework.context.i18n.LocaleContext
* @see org.springframework.context.i18n.TimeZoneAwareLocaleContext
* @see org.springframework.context.i18n.LocaleContextHolder
* @see org.springframework.web.servlet.support.RequestContext#getTimeZone
* @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
*/
public interface LocaleContextResolver extends LocaleResolver {
/**
* Resolve the current locale context via the given request.
* <p>This is primarily intended for framework-level processing; consider using
* {@link org.springframework.web.servlet.support.RequestContextUtils} or
* {@link org.springframework.web.servlet.support.RequestContext} for
* application-level access to the current locale and/or time zone.
* <p>The returned context may be a
* {@link org.springframework.context.i18n.TimeZoneAwareLocaleContext},
* containing a locale with associated time zone information.
* Simply apply an {@code instanceof} check and downcast accordingly.
* <p>Custom resolver implementations may also return extra settings in
* the returned context, which again can be accessed through downcasting.
* @param request the request to resolve the locale context for
* @return the current locale context (never {@code null}
* @see #resolveLocale(HttpServletRequest)
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
*/
LocaleContext resolveLocaleContext(HttpServletRequest request);
/**
* Set the current locale context to the given one,
* potentially including a locale with associated time zone information.
* @param request the request to be used for locale modification
* @param response the response to be used for locale modification
* @param localeContext the new locale context, or {@code null} to clear the locale
* @throws UnsupportedOperationException if the LocaleResolver implementation
* does not support dynamic changing of the locale or time zone
* @see #setLocale(HttpServletRequest, HttpServletResponse, Locale)
* @see org.springframework.context.i18n.SimpleLocaleContext
* @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext
*/
void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext);
}

View File

@@ -34,8 +34,19 @@ import javax.servlet.http.HttpServletResponse;
* to retrieve the current locale in controllers or views, independent
* of the actual resolution strategy.
*
* <p>Note: As of Spring 4.0, there is an extended strategy interface
* called {@link LocaleContextResolver}, allowing for resolution of
* a {@link org.springframework.context.i18n.LocaleContext} object,
* potentially including associated time zone information. Spring's
* provided resolver implementations implement the extended
* {@link LocaleContextResolver} interface wherever appropriate.
*
* @author Juergen Hoeller
* @since 27.02.2003
* @see LocaleContextResolver
* @see org.springframework.context.i18n.LocaleContextHolder
* @see org.springframework.web.servlet.support.RequestContext#getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
public interface LocaleResolver {

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.web.servlet.LocaleContextResolver;
/**
* Abstract base class for {@link LocaleContextResolver} implementations.
* Provides support for a default locale and a default time zone.
*
* <p>Also provides pre-implemented versions of {@link #resolveLocale} and {@link #setLocale},
* delegating to {@link #resolveLocaleContext} and {@link #setLocaleContext}.
*
* @author Juergen Hoeller
* @since 4.0
* @see #setDefaultLocale
* @see #setDefaultTimeZone
*/
public abstract class AbstractLocaleContextResolver extends AbstractLocaleResolver implements LocaleContextResolver {
private TimeZone defaultTimeZone;
/**
* Set a default TimeZone that this resolver will return if no other time zone found.
*/
public void setDefaultTimeZone(TimeZone defaultTimeZone) {
this.defaultTimeZone = defaultTimeZone;
}
/**
* Return the default TimeZone that this resolver is supposed to fall back to, if any.
*/
public TimeZone getDefaultTimeZone() {
return this.defaultTimeZone;
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
return resolveLocaleContext(request).getLocale();
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.web.servlet.LocaleResolver;
*
* @author Juergen Hoeller
* @since 1.2.9
* @see #setDefaultLocale
*/
public abstract class AbstractLocaleResolver implements LocaleResolver {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,16 @@
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.util.CookieGenerator;
import org.springframework.web.util.WebUtils;
@@ -33,29 +37,44 @@ import org.springframework.web.util.WebUtils;
* or the request's accept-header locale.
*
* <p>This is particularly useful for stateless applications without user sessions.
* The cookie may optionally contain an associated time zone value as well;
* alternatively, you may specify a default time zone.
*
* <p>Custom controllers can thus override the user's locale by calling
* {@link #setLocale(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.util.Locale)},
* for example responding to a certain locale change request.
* <p>Custom controllers can override the user's locale and time zone by calling
* {@code #setLocale(Context)} on the resolver, e.g. responding to a locale change
* request. As a more convenient alternative, consider using
* {@link org.springframework.web.servlet.support.RequestContext#changeLocale}.
*
* @author Juergen Hoeller
* @author Jean-Pierre Pawlak
* @since 27.02.2003
* @see #setDefaultLocale
* @see #setLocale
* @see #setDefaultTimeZone
*/
public class CookieLocaleResolver extends CookieGenerator implements LocaleResolver {
public class CookieLocaleResolver extends CookieGenerator implements LocaleContextResolver {
/**
* The name of the request attribute that holds the locale.
* The name of the request attribute that holds the Locale.
* <p>Only used for overriding a cookie value if the locale has been
* changed in the course of the current request! Use
* {@link org.springframework.web.servlet.support.RequestContext#getLocale}
* changed in the course of the current request!
* <p>Use {@code RequestContext(Utils).getLocale()}
* to retrieve the current locale in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
public static final String LOCALE_REQUEST_ATTRIBUTE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
/**
* The name of the request attribute that holds the TimeZone.
* <p>Only used for overriding a cookie value if the locale has been
* changed in the course of the current request!
* <p>Use {@code RequestContext(Utils).getTimeZone()}
* to retrieve the current time zone in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getTimeZone
* @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
*/
public static final String TIME_ZONE_REQUEST_ATTRIBUTE_NAME = CookieLocaleResolver.class.getName() + ".TIME_ZONE";
/**
* The default cookie name used if none is explicitly set.
*/
@@ -64,9 +83,11 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleResol
private Locale defaultLocale;
private TimeZone defaultTimeZone;
/**
* Creates a new instance of the {@link CookieLocaleResolver} class
* Create a new instance of the {@link CookieLocaleResolver} class
* using the {@link #DEFAULT_COOKIE_NAME default cookie name}.
*/
public CookieLocaleResolver() {
@@ -88,45 +109,100 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleResol
return this.defaultLocale;
}
/**
* Set a fixed TimeZone that this resolver will return if no cookie found.
*/
public void setDefaultTimeZone(TimeZone defaultTimeZone) {
this.defaultTimeZone = defaultTimeZone;
}
/**
* Return the fixed TimeZone that this resolver will return if no cookie found,
* if any.
*/
protected TimeZone getDefaultTimeZone() {
return this.defaultTimeZone;
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
// Check request for pre-parsed or preset locale.
Locale locale = (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
if (locale != null) {
return locale;
}
parseLocaleCookieIfNecessary(request);
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
}
// Retrieve and parse cookie value.
Cookie cookie = WebUtils.getCookie(request, getCookieName());
if (cookie != null) {
locale = StringUtils.parseLocaleString(cookie.getValue());
if (logger.isDebugEnabled()) {
logger.debug("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale + "'");
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
parseLocaleCookieIfNecessary(request);
return new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
}
if (locale != null) {
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
return locale;
@Override
public TimeZone getTimeZone() {
return (TimeZone) request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
}
}
};
}
return determineDefaultLocale(request);
private void parseLocaleCookieIfNecessary(HttpServletRequest request) {
if (request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME) == null) {
// Retrieve and parse cookie value.
Cookie cookie = WebUtils.getCookie(request, getCookieName());
Locale locale = null;
TimeZone timeZone = null;
if (cookie != null) {
String value = cookie.getValue();
String localePart = value;
String timeZonePart = null;
int spaceIndex = localePart.indexOf(' ');
if (spaceIndex != -1) {
localePart = value.substring(0, spaceIndex);
timeZonePart = value.substring(spaceIndex + 1);
}
locale = (!"-".equals(localePart) ? StringUtils.parseLocaleString(localePart) : null);
if (timeZonePart != null) {
timeZone = StringUtils.parseTimeZoneString(timeZonePart);
}
if (logger.isDebugEnabled()) {
logger.debug("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale +
"'" + (timeZone != null ? " and time zone '" + timeZone.getID() + "'" : ""));
}
}
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
(locale != null ? locale: determineDefaultLocale(request)));
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
(timeZone != null ? timeZone : determineDefaultTimeZone(request)));
}
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
if (locale != null) {
// Set request attribute and add cookie.
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
addCookie(response, locale.toString());
setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
}
@Override
public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
Locale locale = null;
TimeZone timeZone = null;
if (localeContext != null) {
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
addCookie(response, (locale != null ? locale : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
}
else {
// Set request attribute to fallback locale and remove cookie.
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, determineDefaultLocale(request));
removeCookie(response);
}
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
(locale != null ? locale: determineDefaultLocale(request)));
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
(timeZone != null ? timeZone : determineDefaultTimeZone(request)));
}
/**
* Determine the default locale for the given request,
* Called if no locale cookie has been found.
@@ -145,4 +221,17 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleResol
return defaultLocale;
}
/**
* Determine the default time zone for the given request,
* Called if no TimeZone cookie has been found.
* <p>The default implementation returns the specified default time zone,
* if any, or {@code null} otherwise.
* @param request the request to resolve the time zone for
* @return the default time zone (or {@code null} if none defined)
* @see #setDefaultTimeZone
*/
protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
return getDefaultTimeZone();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,30 +17,36 @@
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
/**
* {@link org.springframework.web.servlet.LocaleResolver} implementation
* that always returns a fixed default locale. Default is the current
* JVM's default locale.
* that always returns a fixed default locale and optionally time zone.
* Default is the current JVM's default locale.
*
* <p>Note: Does not support {@code setLocale}, as the fixed locale
* cannot be changed.
* <p>Note: Does not support {@code setLocale(Context)}, as the fixed
* locale and time zone cannot be changed.
*
* @author Juergen Hoeller
* @since 1.1
* @see #setDefaultLocale
* @see #setDefaultTimeZone
*/
public class FixedLocaleResolver extends AbstractLocaleResolver {
public class FixedLocaleResolver extends AbstractLocaleContextResolver {
/**
* Create a default FixedLocaleResolver, exposing a configured default
* locale (or the JVM's default locale as fallback).
* @see #setDefaultLocale
* @see #setDefaultTimeZone
*/
public FixedLocaleResolver() {
setDefaultLocale(Locale.getDefault());
}
/**
@@ -51,6 +57,16 @@ public class FixedLocaleResolver extends AbstractLocaleResolver {
setDefaultLocale(locale);
}
/**
* Create a FixedLocaleResolver that exposes the given locale and time zone.
* @param locale the locale to expose
* @param timeZone the time zone to expose
*/
public FixedLocaleResolver(Locale locale, TimeZone timeZone) {
setDefaultLocale(locale);
setDefaultTimeZone(timeZone);
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
@@ -62,9 +78,22 @@ public class FixedLocaleResolver extends AbstractLocaleResolver {
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
throw new UnsupportedOperationException(
"Cannot change fixed locale - use a different locale resolution strategy");
public LocaleContext resolveLocaleContext(HttpServletRequest request) {
return new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
return getDefaultLocale();
}
@Override
public TimeZone getTimeZone() {
return getDefaultTimeZone();
}
};
}
@Override
public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,12 @@
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.web.util.WebUtils;
/**
@@ -30,27 +32,41 @@ import org.springframework.web.util.WebUtils;
*
* <p>This is most appropriate if the application needs user sessions anyway,
* that is, when the HttpSession does not have to be created for the locale.
* The session may optionally contain an associated time zone attribute as well;
* alternatively, you may specify a default time zone.
*
* <p>Custom controllers can override the user's locale by calling
* {@code setLocale}, e.g. responding to a locale change request.
* <p>Custom controllers can override the user's locale and time zone by calling
* {@code #setLocale(Context)} on the resolver, e.g. responding to a locale change
* request. As a more convenient alternative, consider using
* {@link org.springframework.web.servlet.support.RequestContext#changeLocale}.
*
* @author Juergen Hoeller
* @since 27.02.2003
* @see #setDefaultLocale
* @see #setLocale
* @see #setDefaultTimeZone
*/
public class SessionLocaleResolver extends AbstractLocaleResolver {
public class SessionLocaleResolver extends AbstractLocaleContextResolver {
/**
* Name of the session attribute that holds the locale.
* Name of the session attribute that holds the Locale.
* Only used internally by this implementation.
* Use {@code RequestContext(Utils).getLocale()}
* <p>Use {@code RequestContext(Utils).getLocale()}
* to retrieve the current locale in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".LOCALE";
/**
* Name of the session attribute that holds the TimeZone.
* Only used internally by this implementation.
* <p>Use {@code RequestContext(Utils).getTimeZone()}
* to retrieve the current time zone in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getTimeZone
* @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
*/
public static final String TIME_ZONE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".TIME_ZONE";
@Override
public Locale resolveLocale(HttpServletRequest request) {
@@ -61,9 +77,46 @@ public class SessionLocaleResolver extends AbstractLocaleResolver {
return locale;
}
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
return new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
if (locale == null) {
locale = determineDefaultLocale(request);
}
return locale;
}
@Override
public TimeZone getTimeZone() {
TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, TIME_ZONE_SESSION_ATTRIBUTE_NAME);
if (timeZone == null) {
timeZone = determineDefaultTimeZone(request);
}
return timeZone;
}
};
}
@Override
public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
Locale locale = null;
TimeZone timeZone = null;
if (localeContext != null) {
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);
WebUtils.setSessionAttribute(request, TIME_ZONE_SESSION_ATTRIBUTE_NAME, timeZone);
}
/**
* Determine the default locale for the given request,
* Called if no locale session attribute has been found.
* Called if no Locale session attribute has been found.
* <p>The default implementation returns the specified default locale,
* if any, else falls back to the request's accept-header locale.
* @param request the request to resolve the locale for
@@ -79,9 +132,17 @@ public class SessionLocaleResolver extends AbstractLocaleResolver {
return defaultLocale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);
/**
* Determine the default time zone for the given request,
* Called if no TimeZone session attribute has been found.
* <p>The default implementation returns the specified default time zone,
* if any, or {@code null} otherwise.
* @param request the request to resolve the time zone for
* @return the default time zone (or {@code null} if none defined)
* @see #setDefaultTimeZone
*/
protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
return getDefaultTimeZone();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,9 @@ import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.security.Principal;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@@ -45,12 +46,15 @@ import org.springframework.web.servlet.support.RequestContextUtils;
* <li>{@link HttpSession}
* <li>{@link Principal}
* <li>{@link Locale}
* <li>{@link TimeZone} (as of Spring 4.0)
* <li>{@link java.time.ZoneId} (as of Spring 4.0 and Java 8)</li>
* <li>{@link InputStream}
* <li>{@link Reader}
* </ul>
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1
*/
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
@@ -64,6 +68,8 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume
HttpSession.class.isAssignableFrom(paramType) ||
Principal.class.isAssignableFrom(paramType) ||
Locale.class.equals(paramType) ||
TimeZone.class.equals(paramType) ||
"java.time.ZoneId".equals(paramType.getName()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType);
}
@@ -97,6 +103,13 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume
else if (Locale.class.equals(paramType)) {
return RequestContextUtils.getLocale(request);
}
else if (TimeZone.class.equals(paramType)) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone : TimeZone.getDefault());
}
else if ("java.time.ZoneId".equals(paramType.getName())) {
return ZoneIdResolver.resolveZoneId(request);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
return request.getInputStream();
}
@@ -110,4 +123,16 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume
}
}
/**
* Inner class to avoid a hard-coded dependency on Java 8's {@link java.time.ZoneId}.
*/
private static class ZoneIdResolver {
public static Object resolveZoneId(HttpServletRequest request) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault());
}
}
}

View File

@@ -18,7 +18,7 @@ package org.springframework.web.servlet.support;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TimeZone;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@@ -80,6 +80,10 @@ public abstract class JstlUtils {
public static void exposeLocalizationContext(HttpServletRequest request, MessageSource messageSource) {
Locale jstlLocale = RequestContextUtils.getLocale(request);
Config.set(request, Config.FMT_LOCALE, jstlLocale);
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
if (timeZone != null) {
Config.set(request, Config.FMT_TIME_ZONE, timeZone);
}
if (messageSource != null) {
LocalizationContext jstlContext = new SpringLocalizationContext(messageSource, request);
Config.set(request, Config.FMT_LOCALIZATION_CONTEXT, jstlContext);
@@ -95,6 +99,10 @@ public abstract class JstlUtils {
*/
public static void exposeLocalizationContext(RequestContext requestContext) {
Config.set(requestContext.getRequest(), Config.FMT_LOCALE, requestContext.getLocale());
TimeZone timeZone = requestContext.getTimeZone();
if (timeZone != null) {
Config.set(requestContext.getRequest(), Config.FMT_TIME_ZONE, timeZone);
}
MessageSource messageSource = getJstlAwareMessageSource(
requestContext.getServletContext(), requestContext.getMessageSource());
LocalizationContext jstlContext = new SpringLocalizationContext(messageSource, requestContext.getRequest());

View File

@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -29,6 +30,9 @@ import javax.servlet.jsp.jstl.core.Config;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.ui.context.Theme;
import org.springframework.ui.context.ThemeSource;
import org.springframework.ui.context.support.ResourceBundleThemeSource;
@@ -40,15 +44,17 @@ import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.EscapedErrors;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ThemeResolver;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
/**
* Context holder for request-specific state, like current web application context, current locale, current theme, and
* potential binding errors. Provides easy access to localized messages and Errors instances.
* Context holder for request-specific state, like current web application context, current locale, current theme,
* and potential binding errors. Provides easy access to localized messages and Errors instances.
*
* <p>Suitable for exposition to views, and usage within JSP's "useBean" tag, JSP scriptlets, JSTL EL, Velocity
* templates, etc. Necessary for views that do not have access to the servlet request, like Velocity templates.
@@ -56,8 +62,8 @@ import org.springframework.web.util.WebUtils;
* <p>Can be instantiated manually, or automatically exposed to views as model attribute via AbstractView's
* "requestContextAttribute" property.
*
* <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using an
* appropriate fallback for the locale (the HttpServletRequest's primary locale).
* <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using
* an appropriate fallback for the locale (the HttpServletRequest's primary locale).
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
@@ -70,15 +76,16 @@ import org.springframework.web.util.WebUtils;
public class RequestContext {
/**
* Default theme name used if the RequestContext cannot find a ThemeResolver. Only applies to non-DispatcherServlet
* requests. <p>Same as AbstractThemeResolver's default, but not linked in here to avoid package interdependencies.
* Default theme name used if the RequestContext cannot find a ThemeResolver.
* Only applies to non-DispatcherServlet requests.
* <p>Same as AbstractThemeResolver's default, but not linked in here to avoid package interdependencies.
* @see org.springframework.web.servlet.theme.AbstractThemeResolver#ORIGINAL_DEFAULT_THEME_NAME
*/
public static final String DEFAULT_THEME_NAME = "theme";
/**
* Request attribute to hold the current web application context for RequestContext usage. By default, the
* DispatcherServlet's context (or the root context as fallback) is exposed.
* Request attribute to hold the current web application context for RequestContext usage.
* By default, the DispatcherServlet's context (or the root context as fallback) is exposed.
*/
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = RequestContext.class.getName() + ".CONTEXT";
@@ -102,6 +109,8 @@ public class RequestContext {
private Locale locale;
private TimeZone timeZone;
private Theme theme;
private Boolean defaultHtmlEscape;
@@ -114,10 +123,11 @@ public class RequestContext {
/**
* Create a new RequestContext for the given request, using the request attributes for Errors retrieval. <p>This
* only works with InternalResourceViews, as Errors instances are part of the model and not normally exposed as
* request attributes. It will typically be used within JSPs or custom tags. <p><b>Will only work within a
* DispatcherServlet request.</b> Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
* Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
* <p>This only works with InternalResourceViews, as Errors instances are part of the model and not
* normally exposed as request attributes. It will typically be used within JSPs or custom tags.
* <p><b>Will only work within a DispatcherServlet request.</b>
* Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
* @param request current HTTP request
* @see org.springframework.web.servlet.DispatcherServlet
* @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.ServletContext)
@@ -127,13 +137,29 @@ public class RequestContext {
}
/**
* Create a new RequestContext for the given request, using the request attributes for Errors retrieval. <p>This
* only works with InternalResourceViews, as Errors instances are part of the model and not normally exposed as
* request attributes. It will typically be used within JSPs or custom tags. <p>If a ServletContext is specified,
* the RequestContext will also work with the root WebApplicationContext (outside a DispatcherServlet).
* Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
* <p>This only works with InternalResourceViews, as Errors instances are part of the model and not
* normally exposed as request attributes. It will typically be used within JSPs or custom tags.
* <p><b>Will only work within a DispatcherServlet request.</b>
* Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
* @param request current HTTP request
* @param servletContext the servlet context of the web application (can be {@code null}; necessary for
* fallback to root WebApplicationContext)
* @param response current HTTP response
* @see org.springframework.web.servlet.DispatcherServlet
* @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext, Map)
*/
public RequestContext(HttpServletRequest request, HttpServletResponse response) {
initContext(request, response, null, null);
}
/**
* Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
* <p>This only works with InternalResourceViews, as Errors instances are part of the model and not
* normally exposed as request attributes. It will typically be used within JSPs or custom tags.
* <p>If a ServletContext is specified, the RequestContext will also work with the root
* WebApplicationContext (outside a DispatcherServlet).
* @param request current HTTP request
* @param servletContext the servlet context of the web application (can be {@code null};
* necessary for fallback to root WebApplicationContext)
* @see org.springframework.web.context.WebApplicationContext
* @see org.springframework.web.servlet.DispatcherServlet
*/
@@ -142,13 +168,13 @@ public class RequestContext {
}
/**
* Create a new RequestContext for the given request, using the given model attributes for Errors retrieval. <p>This
* works with all View implementations. It will typically be used by View implementations. <p><b>Will only work
* within a DispatcherServlet request.</b> Pass in a ServletContext to be able to fallback to the root
* WebApplicationContext.
* Create a new RequestContext for the given request, using the given model attributes for Errors retrieval.
* <p>This works with all View implementations. It will typically be used by View implementations.
* <p><b>Will only work within a DispatcherServlet request.</b>
* Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
* @param request current HTTP request
* @param model the model attributes for the current view (can be {@code null}, using the request attributes
* for Errors retrieval)
* @param model the model attributes for the current view (can be {@code null},
* using the request attributes for Errors retrieval)
* @see org.springframework.web.servlet.DispatcherServlet
* @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext, Map)
*/
@@ -157,9 +183,10 @@ public class RequestContext {
}
/**
* Create a new RequestContext for the given request, using the given model attributes for Errors retrieval. <p>This
* works with all View implementations. It will typically be used by View implementations. <p>If a ServletContext is
* specified, the RequestContext will also work with a root WebApplicationContext (outside a DispatcherServlet).
* Create a new RequestContext for the given request, using the given model attributes for Errors retrieval.
* <p>This works with all View implementations. It will typically be used by View implementations.
* <p>If a ServletContext is specified, the RequestContext will also work with a root
* WebApplicationContext (outside a DispatcherServlet).
* @param request current HTTP request
* @param response current HTTP response
* @param servletContext the servlet context of the web application (can be {@code null}; necessary for
@@ -212,14 +239,25 @@ public class RequestContext {
// Determine locale to use for this RequestContext.
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver != null) {
if (localeResolver instanceof LocaleContextResolver) {
LocaleContext localeContext = ((LocaleContextResolver) localeResolver).resolveLocaleContext(request);
this.locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
this.timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
else if (localeResolver != null) {
// Try LocaleResolver (we're within a DispatcherServlet request).
this.locale = localeResolver.resolveLocale(request);
}
else {
// No LocaleResolver available -> try fallback.
// Try JSTL fallbacks if necessary.
if (this.locale == null) {
this.locale = getFallbackLocale();
}
if (this.timeZone == null) {
this.timeZone = getFallbackTimeZone();
}
// Determine default HTML escape setting from the "defaultHtmlEscape"
// context-param in web.xml, if any.
@@ -235,9 +273,8 @@ public class RequestContext {
/**
* Determine the fallback locale for this context.
* <p>The default implementation checks for a JSTL locale attribute in request,
* session or application scope; if not found, returns the
* {@code HttpServletRequest.getLocale()}.
* <p>The default implementation checks for a JSTL locale attribute in request, session
* or application scope; if not found, returns the {@code HttpServletRequest.getLocale()}.
* @return the fallback locale (never {@code null})
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
@@ -251,6 +288,22 @@ public class RequestContext {
return getRequest().getLocale();
}
/**
* Determine the fallback time zone for this context.
* <p>The default implementation checks for a JSTL time zone attribute in request,
* session or application scope; returns {@code null} if not found.
* @return the fallback time zone (or {@code null} if none derivable from the request)
*/
protected TimeZone getFallbackTimeZone() {
if (jstlPresent) {
TimeZone timeZone = JstlLocaleResolver.getJstlTimeZone(getRequest(), getServletContext());
if (timeZone != null) {
return timeZone;
}
}
return null;
}
/**
* Determine the fallback theme for this context.
* <p>The default implementation returns the default theme (with name "theme").
@@ -306,17 +359,65 @@ public class RequestContext {
}
/**
* Return the current Locale (never {@code null}).
* Return the current Locale (falling back to the request locale; never {@code null}).
* <p>Typically coming from a DispatcherServlet's {@link LocaleResolver}.
* Also includes a fallback check for JSTL's Locale attribute.
* @see RequestContextUtils#getLocale
*/
public final Locale getLocale() {
return this.locale;
}
/**
* Return the current TimeZone (or {@code null} if none derivable from the request).
* <p>Typically coming from a DispatcherServlet's {@link LocaleContextResolver}.
* Also includes a fallback check for JSTL's TimeZone attribute.
* @see RequestContextUtils#getTimeZone
*/
public TimeZone getTimeZone() {
return this.timeZone;
}
/**
* Change the current locale to the specified one,
* storing the new locale through the configured {@link LocaleResolver}.
* @param locale the new locale
* @see LocaleResolver#setLocale
* @see #changeLocale(java.util.Locale, java.util.TimeZone)
*/
public void changeLocale(Locale locale) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(this.request);
if (localeResolver == null) {
throw new IllegalStateException("Cannot change locale if no LocaleResolver configured");
}
localeResolver.setLocale(this.request, this.response, locale);
this.locale = locale;
}
/**
* Change the current locale to the specified locale and time zone context,
* storing the new locale context through the configured {@link LocaleResolver}.
* @param locale the new locale
* @param timeZone the new time zone
* @see LocaleContextResolver#setLocaleContext
* @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext
*/
public void changeLocale(Locale locale, TimeZone timeZone) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(this.request);
if (!(localeResolver instanceof LocaleContextResolver)) {
throw new IllegalStateException("Cannot change locale context if no LocaleContextResolver configured");
}
((LocaleContextResolver) localeResolver).setLocaleContext(this.request, this.response,
new SimpleTimeZoneAwareLocaleContext(locale, timeZone));
this.locale = locale;
this.timeZone = timeZone;
}
/**
* Return the current theme (never {@code null}).
* <p>Resolved lazily for more efficiency when theme support is not being used.
*/
public final Theme getTheme() {
public Theme getTheme() {
if (this.theme == null) {
// Lazily determine theme to use for this RequestContext.
this.theme = RequestContextUtils.getTheme(this.request);
@@ -329,8 +430,39 @@ public class RequestContext {
}
/**
* (De)activate default HTML escaping for messages and errors, for the scope of this RequestContext. The default is
* the application-wide setting (the "defaultHtmlEscape" context-param in web.xml).
* Change the current theme to the specified one,
* storing the new theme name through the configured {@link ThemeResolver}.
* @param theme the new theme
* @see ThemeResolver#setThemeName
*/
public void changeTheme(Theme theme) {
ThemeResolver themeResolver = RequestContextUtils.getThemeResolver(this.request);
if (themeResolver == null) {
throw new IllegalStateException("Cannot change theme if no ThemeResolver configured");
}
themeResolver.setThemeName(this.request, this.response, (theme != null ? theme.getName() : null));
this.theme = theme;
}
/**
* Change the current theme to the specified theme by name,
* storing the new theme name through the configured {@link ThemeResolver}.
* @param themeName the name of the new theme
* @see ThemeResolver#setThemeName
*/
public void changeTheme(String themeName) {
ThemeResolver themeResolver = RequestContextUtils.getThemeResolver(this.request);
if (themeResolver == null) {
throw new IllegalStateException("Cannot change theme if no ThemeResolver configured");
}
themeResolver.setThemeName(this.request, this.response, themeName);
// Ask for re-resolution on next getTheme call.
this.theme = null;
}
/**
* (De)activate default HTML escaping for messages and errors, for the scope of this RequestContext.
* <p>The default is the application-wide setting (the "defaultHtmlEscape" context-param in web.xml).
* @see org.springframework.web.util.WebUtils#isDefaultHtmlEscape
*/
public void setDefaultHtmlEscape(boolean defaultHtmlEscape) {
@@ -353,8 +485,8 @@ public class RequestContext {
}
/**
* Set the UrlPathHelper to use for context path and request URI decoding. Can be used to pass a shared
* UrlPathHelper instance in.
* Set the UrlPathHelper to use for context path and request URI decoding.
* Can be used to pass a shared UrlPathHelper instance in.
* <p>A default UrlPathHelper is always available.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
@@ -363,8 +495,8 @@ public class RequestContext {
}
/**
* Return the UrlPathHelper used for context path and request URI decoding. Can be used to configure the current
* UrlPathHelper.
* Return the UrlPathHelper used for context path and request URI decoding.
* Can be used to configure the current UrlPathHelper.
* <p>A default UrlPathHelper is always available.
*/
public UrlPathHelper getUrlPathHelper() {
@@ -381,8 +513,9 @@ public class RequestContext {
}
/**
* Return the context path of the original request, that is, the path that indicates the current web application.
* This is useful for building links to other resources within the application.
* Return the context path of the original request, that is, the path that
* indicates the current web application. This is useful for building links
* to other resources within the application.
* <p>Delegates to the UrlPathHelper for decoding.
* @see javax.servlet.http.HttpServletRequest#getContextPath
* @see #getUrlPathHelper
@@ -408,7 +541,6 @@ public class RequestContext {
* Return a context-aware URl for the given relative URL with placeholders (named keys with braces {@code {}}).
* For example, send in a relative URL {@code foo/{bar}?spam={spam}} and a parameter map
* {@code {bar=baz,spam=nuts}} and the result will be {@code [contextpath]/foo/baz?spam=nuts}.
*
* @param relativeUrl the relative URL part
* @param params a map of parameters to insert as placeholders in the url
* @return a URL that points back to the server with an absolute path (also URL-encoded accordingly)
@@ -439,10 +571,9 @@ public class RequestContext {
}
/**
* Return the request URI of the original request, that is, the invoked URL without parameters. This is particularly
* useful as HTML form action target, possibly in combination with the original query string. <p><b>Note this
* implementation will correctly resolve to the URI of any originating root request in the presence of a forwarded
* request. However, this can only work when the Servlet 2.4 'forward' request attributes are present.
* Return the request URI of the original request, that is, the invoked URL
* without parameters. This is particularly useful as HTML form action target,
* possibly in combination with the original query string.
* <p>Delegates to the UrlPathHelper for decoding.
* @see #getQueryString
* @see org.springframework.web.util.UrlPathHelper#getOriginatingRequestUri
@@ -453,10 +584,9 @@ public class RequestContext {
}
/**
* Return the query string of the current request, that is, the part after the request path. This is particularly
* useful for building an HTML form action target in combination with the original request URI. <p><b>Note this
* implementation will correctly resolve to the query string of any originating root request in the presence of a
* forwarded request. However, this can only work when the Servlet 2.4 'forward' request attributes are present.
* Return the query string of the current request, that is, the part after
* the request path. This is particularly useful for building an HTML form
* action target in combination with the original request URI.
* <p>Delegates to the UrlPathHelper for decoding.
* @see #getRequestUri
* @see org.springframework.web.util.UrlPathHelper#getOriginatingQueryString
@@ -768,6 +898,20 @@ public class RequestContext {
}
return (localeObject instanceof Locale ? (Locale) localeObject : null);
}
public static TimeZone getJstlTimeZone(HttpServletRequest request, ServletContext servletContext) {
Object timeZoneObject = Config.get(request, Config.FMT_TIME_ZONE);
if (timeZoneObject == null) {
HttpSession session = request.getSession(false);
if (session != null) {
timeZoneObject = Config.get(session, Config.FMT_TIME_ZONE);
}
if (timeZoneObject == null && servletContext != null) {
timeZoneObject = Config.get(servletContext, Config.FMT_TIME_ZONE);
}
}
return (timeZoneObject instanceof TimeZone ? (TimeZone) timeZoneObject : null);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,13 @@ package org.springframework.web.servlet.support;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.ui.context.Theme;
import org.springframework.ui.context.ThemeSource;
import org.springframework.web.context.WebApplicationContext;
@@ -30,6 +32,7 @@ import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ThemeResolver;
@@ -98,23 +101,51 @@ public abstract class RequestContextUtils {
}
/**
* Retrieves the current locale from the given request,
* using the LocaleResolver bound to the request by the DispatcherServlet
* Retrieve the current locale from the given request, using the
* LocaleResolver bound to the request by the DispatcherServlet
* (if available), falling back to the request's accept-header Locale.
* <p>This method serves as a straightforward alternative to the standard
* Servlet {@link javax.servlet.http.HttpServletRequest#getLocale()} method,
* falling back to the latter if no more specific locale has been found.
* <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getLocale()}
* which will normally be populated with the same Locale.
* @param request current HTTP request
* @return the current locale, either from the LocaleResolver or from
* the plain request
* @return the current locale for the given request, either from the
* LocaleResolver or from the plain request itself
* @see #getLocaleResolver
* @see javax.servlet.http.HttpServletRequest#getLocale()
* @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
*/
public static Locale getLocale(HttpServletRequest request) {
LocaleResolver localeResolver = getLocaleResolver(request);
if (localeResolver != null) {
return localeResolver.resolveLocale(request);
}
else {
return request.getLocale();
return (localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale());
}
/**
* Retrieve the current time zone from the given request, using the
* TimeZoneAwareLocaleResolver bound to the request by the DispatcherServlet
* (if available), falling back to the system's default time zone.
* <p>Note: This method returns {@code null} if no specific time zone can be
* resolved for the given request. This is in contrast to {@link #getLocale}
* where there is always the request's accept-header locale to fall back to.
* <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getTimeZone()}
* which will normally be populated with the same TimeZone: That method only
* differs in terms of its fallback to the system time zone if the LocaleResolver
* hasn't provided provided a specific time zone (instead of this method's {@code null}).
* @param request current HTTP request
* @return the current time zone for the given request, either from the
* TimeZoneAwareLocaleResolver or {@code null} if none associated
* @see #getLocaleResolver
* @see org.springframework.context.i18n.LocaleContextHolder#getTimeZone()
*/
public static TimeZone getTimeZone(HttpServletRequest request) {
LocaleResolver localeResolver = getLocaleResolver(request);
if (localeResolver instanceof LocaleContextResolver) {
LocaleContext localeContext = ((LocaleContextResolver) localeResolver).resolveLocaleContext(request);
if (localeContext instanceof TimeZoneAwareLocaleContext) {
return ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
return null;
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,10 @@ import java.sql.SQLException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
@@ -580,14 +582,19 @@ public abstract class AbstractJasperReportsView extends AbstractUrlBasedView {
*/
protected void exposeLocalizationContext(Map<String, Object> model, HttpServletRequest request) {
RequestContext rc = new RequestContext(request, getServletContext());
Locale locale = rc.getLocale();
if (!model.containsKey(JRParameter.REPORT_LOCALE)) {
model.put(JRParameter.REPORT_LOCALE, rc.getLocale());
model.put(JRParameter.REPORT_LOCALE, locale);
}
TimeZone timeZone = rc.getTimeZone();
if (timeZone != null && !model.containsKey(JRParameter.REPORT_TIME_ZONE)) {
model.put(JRParameter.REPORT_TIME_ZONE, timeZone);
}
JasperReport report = getReport();
if ((report == null || report.getResourceBundle() == null) &&
!model.containsKey(JRParameter.REPORT_RESOURCE_BUNDLE)) {
model.put(JRParameter.REPORT_RESOURCE_BUNDLE,
new MessageSourceResourceBundle(rc.getMessageSource(), rc.getLocale()));
new MessageSourceResourceBundle(rc.getMessageSource(), locale));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.web.servlet.view.velocity;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -408,12 +409,11 @@ public class VelocityView extends AbstractTemplateView {
// Expose locale-aware DateTool/NumberTool attributes.
if (this.dateToolAttribute != null || this.numberToolAttribute != null) {
Locale locale = RequestContextUtils.getLocale(request);
if (this.dateToolAttribute != null) {
velocityContext.put(this.dateToolAttribute, new LocaleAwareDateTool(locale));
velocityContext.put(this.dateToolAttribute, new LocaleAwareDateTool(request));
}
if (this.numberToolAttribute != null) {
velocityContext.put(this.numberToolAttribute, new LocaleAwareNumberTool(locale));
velocityContext.put(this.numberToolAttribute, new LocaleAwareNumberTool(request));
}
}
}
@@ -528,41 +528,48 @@ public class VelocityView extends AbstractTemplateView {
/**
* Subclass of DateTool from Velocity Tools, using a passed-in Locale
* (usually the RequestContext Locale) instead of the default Locale.N
* Subclass of DateTool from Velocity Tools, using a Spring-resolved
* Locale and TimeZone instead of the default Locale.
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
*/
private static class LocaleAwareDateTool extends DateTool {
private final Locale locale;
private final HttpServletRequest request;
private LocaleAwareDateTool(Locale locale) {
this.locale = locale;
public LocaleAwareDateTool(HttpServletRequest request) {
this.request = request;
}
@Override
public Locale getLocale() {
return this.locale;
return RequestContextUtils.getLocale(this.request);
}
@Override
public TimeZone getTimeZone() {
TimeZone timeZone = RequestContextUtils.getTimeZone(this.request);
return (timeZone != null ? timeZone : super.getTimeZone());
}
}
/**
* Subclass of NumberTool from Velocity Tools, using a passed-in Locale
* (usually the RequestContext Locale) instead of the default Locale.
* Subclass of NumberTool from Velocity Tools, using a Spring-resolved
* Locale instead of the default Locale.
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
private static class LocaleAwareNumberTool extends NumberTool {
private final Locale locale;
private final HttpServletRequest request;
private LocaleAwareNumberTool(Locale locale) {
this.locale = locale;
public LocaleAwareNumberTool(HttpServletRequest request) {
this.request = request;
}
@Override
public Locale getLocale() {
return this.locale;
return RequestContextUtils.getLocale(this.request);
}
}