/*
* Copyright 2002-2018 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.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.lang.Nullable;
import org.springframework.util.Assert;
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;
LocaleResolver
implementation that uses a cookie sent back to the user in case of a custom setting, with a fallback to the specified default locale or the request's accept-header locale. 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.
Custom controllers can override the user's locale and time zone by calling #setLocale(Context)
on the resolver, e.g. responding to a locale change request. As a more convenient alternative, consider using RequestContext.changeLocale
.
Author: Juergen Hoeller, Jean-Pierre Pawlak See Also: Since: 27.02.2003
/**
* {@link LocaleResolver} implementation that uses a cookie sent back to the user
* in case of a custom setting, with a fallback to the specified default locale
* 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 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 #setDefaultTimeZone
*/
public class CookieLocaleResolver extends CookieGenerator implements LocaleContextResolver {
The name of the request attribute that holds the Locale.
Only used for overriding a cookie value if the locale has been
changed in the course of the current request!
Use RequestContext(Utils).getLocale()
to retrieve the current locale in controllers or views.
See Also:
/**
* 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!
* <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.
Only used for overriding a cookie value if the locale has been
changed in the course of the current request!
Use RequestContext(Utils).getTimeZone()
to retrieve the current time zone in controllers or views.
See Also:
/**
* 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.
/**
* The default cookie name used if none is explicitly set.
*/
public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
private boolean languageTagCompliant = true;
@Nullable
private Locale defaultLocale;
@Nullable
private TimeZone defaultTimeZone;
Create a new instance of the CookieLocaleResolver
class using the default cookie name
. /**
* Create a new instance of the {@link CookieLocaleResolver} class
* using the {@link #DEFAULT_COOKIE_NAME default cookie name}.
*/
public CookieLocaleResolver() {
setCookieName(DEFAULT_COOKIE_NAME);
}
Specify whether this resolver's cookies should be compliant with BCP 47
language tags instead of Java's legacy locale specification format.
The default is true
, as of 5.1. Switch this to false
for rendering Java's legacy locale specification format. For parsing, this resolver leniently accepts the legacy Locale.toString
format as well as BCP 47 language tags in any case.
See Also: Since: 4.3
/**
* Specify whether this resolver's cookies should be compliant with BCP 47
* language tags instead of Java's legacy locale specification format.
* <p>The default is {@code true}, as of 5.1. Switch this to {@code false}
* for rendering Java's legacy locale specification format. For parsing,
* this resolver leniently accepts the legacy {@link Locale#toString}
* format as well as BCP 47 language tags in any case.
* @since 4.3
* @see #parseLocaleValue(String)
* @see #toLocaleValue(Locale)
* @see Locale#forLanguageTag(String)
* @see Locale#toLanguageTag()
*/
public void setLanguageTagCompliant(boolean languageTagCompliant) {
this.languageTagCompliant = languageTagCompliant;
}
Return whether this resolver's cookies should be compliant with BCP 47
language tags instead of Java's legacy locale specification format.
Since: 4.3
/**
* Return whether this resolver's cookies should be compliant with BCP 47
* language tags instead of Java's legacy locale specification format.
* @since 4.3
*/
public boolean isLanguageTagCompliant() {
return this.languageTagCompliant;
}
Set a fixed Locale that this resolver will return if no cookie found.
/**
* Set a fixed Locale that this resolver will return if no cookie found.
*/
public void setDefaultLocale(@Nullable Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
Return the fixed Locale that this resolver will return if no cookie found,
if any.
/**
* Return the fixed Locale that this resolver will return if no cookie found,
* if any.
*/
@Nullable
protected Locale getDefaultLocale() {
return this.defaultLocale;
}
Set a fixed TimeZone that this resolver will return if no cookie found.
Since: 4.0
/**
* Set a fixed TimeZone that this resolver will return if no cookie found.
* @since 4.0
*/
public void setDefaultTimeZone(@Nullable TimeZone defaultTimeZone) {
this.defaultTimeZone = defaultTimeZone;
}
Return the fixed TimeZone that this resolver will return if no cookie found,
if any.
Since: 4.0
/**
* Return the fixed TimeZone that this resolver will return if no cookie found,
* if any.
* @since 4.0
*/
@Nullable
protected TimeZone getDefaultTimeZone() {
return this.defaultTimeZone;
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
parseLocaleCookieIfNecessary(request);
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
}
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
parseLocaleCookieIfNecessary(request);
return new TimeZoneAwareLocaleContext() {
@Override
@Nullable
public Locale getLocale() {
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
}
@Override
@Nullable
public TimeZone getTimeZone() {
return (TimeZone) request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
}
};
}
private void parseLocaleCookieIfNecessary(HttpServletRequest request) {
if (request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME) == null) {
Locale locale = null;
TimeZone timeZone = null;
// Retrieve and parse cookie value.
String cookieName = getCookieName();
if (cookieName != null) {
Cookie cookie = WebUtils.getCookie(request, cookieName);
if (cookie != null) {
String value = cookie.getValue();
String localePart = value;
String timeZonePart = null;
int separatorIndex = localePart.indexOf('/');
if (separatorIndex == -1) {
// Leniently accept older cookies separated by a space...
separatorIndex = localePart.indexOf(' ');
}
if (separatorIndex >= 0) {
localePart = value.substring(0, separatorIndex);
timeZonePart = value.substring(separatorIndex + 1);
}
try {
locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null);
if (timeZonePart != null) {
timeZone = StringUtils.parseTimeZoneString(timeZonePart);
}
}
catch (IllegalArgumentException ex) {
String cookieDescription = "invalid locale cookie '" + cookieName +
"': [" + value + "] due to: " + ex.getMessage();
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
// Error dispatch: ignore locale/timezone parse exceptions
if (logger.isDebugEnabled()) {
logger.debug("Ignoring " + cookieDescription);
}
}
else {
throw new IllegalStateException("Encountered " + cookieDescription);
}
}
if (logger.isTraceEnabled()) {
logger.trace("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, @Nullable HttpServletResponse response, @Nullable Locale locale) {
setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
}
@Override
public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response,
@Nullable LocaleContext localeContext) {
Assert.notNull(response, "HttpServletResponse is required for CookieLocaleResolver");
Locale locale = null;
TimeZone timeZone = null;
if (localeContext != null) {
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
addCookie(response,
(locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? '/' + timeZone.getID() : ""));
}
else {
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)));
}
Parse the given locale value coming from an incoming cookie.
The default implementation calls StringUtils.parseLocale(String)
, accepting the Locale.toString
format as well as BCP 47 language tags.
Params: - localeValue – the locale value to parse
See Also: Returns: the corresponding Locale
instance Since: 4.3
/**
* Parse the given locale value coming from an incoming cookie.
* <p>The default implementation calls {@link StringUtils#parseLocale(String)},
* accepting the {@link Locale#toString} format as well as BCP 47 language tags.
* @param localeValue the locale value to parse
* @return the corresponding {@code Locale} instance
* @since 4.3
* @see StringUtils#parseLocale(String)
*/
@Nullable
protected Locale parseLocaleValue(String localeValue) {
return StringUtils.parseLocale(localeValue);
}
Render the given locale as a text value for inclusion in a cookie.
The default implementation calls Locale.toString()
or JDK 7's Locale.toLanguageTag()
, depending on the "languageTagCompliant"
configuration property.
Params: - locale – the locale to stringify
See Also: Returns: a String representation for the given locale Since: 4.3
/**
* Render the given locale as a text value for inclusion in a cookie.
* <p>The default implementation calls {@link Locale#toString()}
* or JDK 7's {@link Locale#toLanguageTag()}, depending on the
* {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
* @param locale the locale to stringify
* @return a String representation for the given locale
* @since 4.3
* @see #isLanguageTagCompliant()
*/
protected String toLocaleValue(Locale locale) {
return (isLanguageTagCompliant() ? locale.toLanguageTag() : locale.toString());
}
Determine the default locale for the given request,
Called if no locale cookie has been found.
The default implementation returns the specified default locale,
if any, else falls back to the request's accept-header locale.
Params: - request – the request to resolve the locale for
See Also: Returns: the default locale (never null
)
/**
* Determine the default locale for the given request,
* Called if no locale cookie 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
* @return the default locale (never {@code null})
* @see #setDefaultLocale
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
@Nullable
protected Locale determineDefaultLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale == null) {
defaultLocale = request.getLocale();
}
return defaultLocale;
}
Determine the default time zone for the given request,
Called if no TimeZone cookie has been found.
The default implementation returns the specified default time zone, if any, or null
otherwise.
Params: - request – the request to resolve the time zone for
See Also: Returns: the default time zone (or null
if none defined)
/**
* 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
*/
@Nullable
protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
return getDefaultTimeZone();
}
}