diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleContextResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleContextResolver.java index 0140cf192ba2..8d6b4f0caaea 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleContextResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleContextResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -22,12 +22,16 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.SimpleLocaleContext; import org.springframework.lang.Nullable; /** * Extension of {@link LocaleResolver}, adding support for a rich locale context * (potentially including locale and time zone information). * + *

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 org.springframework.context.i18n.LocaleContext @@ -73,4 +77,15 @@ public interface LocaleContextResolver extends LocaleResolver { void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable LocaleContext localeContext); + @Override + default Locale resolveLocale(HttpServletRequest request) { + Locale locale = resolveLocaleContext(request).getLocale(); + return (locale != null ? locale : request.getLocale()); + } + + @Override + default void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) { + setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null)); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleContextResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleContextResolver.java index 98162f6fecbb..234ae1053cd0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleContextResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleContextResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -16,13 +16,8 @@ package org.springframework.web.servlet.i18n; -import java.util.Locale; import java.util.TimeZone; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.context.i18n.SimpleLocaleContext; import org.springframework.lang.Nullable; import org.springframework.web.servlet.LocaleContextResolver; @@ -30,9 +25,6 @@ * Abstract base class for {@link LocaleContextResolver} implementations. * Provides support for a default locale and a default time zone. * - *

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 @@ -59,16 +51,4 @@ public TimeZone getDefaultTimeZone() { return this.defaultTimeZone; } - - @Override - public Locale resolveLocale(HttpServletRequest request) { - Locale locale = resolveLocaleContext(request).getLocale(); - return (locale != null ? locale : request.getLocale()); - } - - @Override - public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) { - setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null)); - } - } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java index 8df4c6c51df4..47cae61aab12 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -33,7 +33,7 @@ * specified in the "accept-language" header of the HTTP request (that is, * the locale sent by the client browser, normally that of the client's OS). * - *

Note: Does not support {@code setLocale}, since the accept header + *

Note: Does not support {@code setLocale}, since the "accept-header" * can only be changed through changing the client's locale settings. * * @author Juergen Hoeller @@ -41,13 +41,10 @@ * @since 27.02.2003 * @see jakarta.servlet.http.HttpServletRequest#getLocale() */ -public class AcceptHeaderLocaleResolver implements LocaleResolver { +public class AcceptHeaderLocaleResolver extends AbstractLocaleResolver { private final List supportedLocales = new ArrayList<>(4); - @Nullable - private Locale defaultLocale; - /** * Configure supported locales to check against the requested locales @@ -69,29 +66,6 @@ public List getSupportedLocales() { return this.supportedLocales; } - /** - * Configure a fixed default locale to fall back on if the request does not - * have an "Accept-Language" header. - *

By default this is not set in which case when there is no "Accept-Language" - * header, the default locale for the server is used as defined in - * {@link HttpServletRequest#getLocale()}. - * @param defaultLocale the default locale to use - * @since 4.3 - */ - public void setDefaultLocale(@Nullable Locale defaultLocale) { - this.defaultLocale = defaultLocale; - } - - /** - * The configured default locale, if any. - *

This method may be overridden in subclasses. - * @since 4.3 - */ - @Nullable - public Locale getDefaultLocale() { - return this.defaultLocale; - } - @Override public Locale resolveLocale(HttpServletRequest request) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java index c4d9a85ba711..727be6ecd16d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java @@ -18,6 +18,7 @@ import java.util.Locale; import java.util.TimeZone; +import java.util.function.Function; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -50,6 +51,7 @@ * * @author Juergen Hoeller * @author Jean-Pierre Pawlak + * @author Vedran Pavic * @since 27.02.2003 * @see #setDefaultLocale * @see #setDefaultTimeZone @@ -94,6 +96,15 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @Nullable private TimeZone defaultTimeZone; + private Function defaultLocaleFunction = request -> { + Locale defaultLocale = getDefaultLocale(); + if (defaultLocale == null) { + defaultLocale = request.getLocale(); + } + return defaultLocale; + }; + + private Function defaultTimeZoneFunction = request -> getDefaultTimeZone(); /** * Create a new instance of the {@link CookieLocaleResolver} class @@ -137,8 +148,8 @@ public boolean isLanguageTagCompliant() { * @since 5.1.7 * @see #setDefaultLocale * @see #setDefaultTimeZone - * @see #determineDefaultLocale - * @see #determineDefaultTimeZone + * @see #setDefaultLocaleFunction(Function) + * @see #setDefaultTimeZoneFunction(Function) */ public void setRejectInvalidCookies(boolean rejectInvalidCookies) { this.rejectInvalidCookies = rejectInvalidCookies; @@ -186,6 +197,35 @@ protected TimeZone getDefaultTimeZone() { return this.defaultTimeZone; } + /** + * Set the function used to determine the default locale for the given request, + * called if no {@link Locale} session attribute has been found. + *

The default implementation returns the specified default locale, + * if any, else falls back to the request's accept-header locale. + * @param defaultLocaleFunction the function used to determine the default locale + * @since 6.0 + * @see #setDefaultLocale + * @see jakarta.servlet.http.HttpServletRequest#getLocale() + */ + public void setDefaultLocaleFunction(Function defaultLocaleFunction) { + Assert.notNull(defaultLocaleFunction, "defaultLocaleFunction must not be null"); + this.defaultLocaleFunction = defaultLocaleFunction; + } + + /** + * Set the function used to determine the default time zone for the given request, + * called if no {@link TimeZone} session attribute has been found. + *

The default implementation returns the specified default time zone, + * if any, or {@code null} otherwise. + * @param defaultTimeZoneFunction the function used to determine the default time zone + * @since 6.0 + * @see #setDefaultTimeZone + */ + public void setDefaultTimeZoneFunction(Function defaultTimeZoneFunction) { + Assert.notNull(defaultTimeZoneFunction, "defaultTimeZoneFunction must not be null"); + this.defaultTimeZoneFunction = defaultTimeZoneFunction; + } + @Override public Locale resolveLocale(HttpServletRequest request) { @@ -260,9 +300,9 @@ private void parseLocaleCookieIfNecessary(HttpServletRequest request) { } request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, - (locale != null ? locale : determineDefaultLocale(request))); + (locale != null ? locale : this.defaultLocaleFunction.apply(request))); request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME, - (timeZone != null ? timeZone : determineDefaultTimeZone(request))); + (timeZone != null ? timeZone : this.defaultTimeZoneFunction.apply(request))); } } @@ -291,9 +331,9 @@ public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletRe removeCookie(response); } request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, - (locale != null ? locale : determineDefaultLocale(request))); + (locale != null ? locale : this.defaultLocaleFunction.apply(request))); request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME, - (timeZone != null ? timeZone : determineDefaultTimeZone(request))); + (timeZone != null ? timeZone : this.defaultTimeZoneFunction.apply(request))); } @@ -334,13 +374,11 @@ protected String toLocaleValue(Locale locale) { * @return the default locale (never {@code null}) * @see #setDefaultLocale * @see jakarta.servlet.http.HttpServletRequest#getLocale() + * @deprecated as of 6.0, in favor of {@link #setDefaultLocaleFunction(Function)} */ + @Deprecated protected Locale determineDefaultLocale(HttpServletRequest request) { - Locale defaultLocale = getDefaultLocale(); - if (defaultLocale == null) { - defaultLocale = request.getLocale(); - } - return defaultLocale; + return this.defaultLocaleFunction.apply(request); } /** @@ -351,10 +389,12 @@ protected Locale determineDefaultLocale(HttpServletRequest request) { * @param request the request to resolve the time zone for * @return the default time zone (or {@code null} if none defined) * @see #setDefaultTimeZone + * @deprecated as of 6.0, in favor of {@link #setDefaultTimeZoneFunction(Function)} */ + @Deprecated @Nullable protected TimeZone determineDefaultTimeZone(HttpServletRequest request) { - return getDefaultTimeZone(); + return this.defaultTimeZoneFunction.apply(request); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java index 7ae9042a09d0..75a8a7d7dbb1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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 @@ import java.util.Locale; import java.util.TimeZone; +import java.util.function.Function; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -25,6 +26,7 @@ import org.springframework.context.i18n.LocaleContext; import org.springframework.context.i18n.TimeZoneAwareLocaleContext; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.web.util.WebUtils; /** @@ -54,6 +56,7 @@ * against the current {@code HttpServletRequest}. * * @author Juergen Hoeller + * @author Vedran Pavic * @since 27.02.2003 * @see #setDefaultLocale * @see #setDefaultTimeZone @@ -85,6 +88,15 @@ public class SessionLocaleResolver extends AbstractLocaleContextResolver { private String timeZoneAttributeName = TIME_ZONE_SESSION_ATTRIBUTE_NAME; + private Function defaultLocaleFunction = request -> { + Locale defaultLocale = getDefaultLocale(); + if (defaultLocale == null) { + defaultLocale = request.getLocale(); + } + return defaultLocale; + }; + + private Function defaultTimeZoneFunction = request -> getDefaultTimeZone(); /** * Specify the name of the corresponding attribute in the {@code HttpSession}, @@ -106,12 +118,40 @@ public void setTimeZoneAttributeName(String timeZoneAttributeName) { this.timeZoneAttributeName = timeZoneAttributeName; } + /** + * Set the function used to determine the default locale for the given request, + * called if no {@link Locale} session attribute has been found. + *

The default implementation returns the specified default locale, + * if any, else falls back to the request's accept-header locale. + * @param defaultLocaleFunction the function used to determine the default locale + * @since 6.0 + * @see #setDefaultLocale + * @see jakarta.servlet.http.HttpServletRequest#getLocale() + */ + public void setDefaultLocaleFunction(Function defaultLocaleFunction) { + Assert.notNull(defaultLocaleFunction, "defaultLocaleFunction must not be null"); + this.defaultLocaleFunction = defaultLocaleFunction; + } + + /** + * Set the function used to determine the default time zone for the given request, + * called if no {@link TimeZone} session attribute has been found. + *

The default implementation returns the specified default time zone, + * if any, or {@code null} otherwise. + * @param defaultTimeZoneFunction the function used to determine the default time zone + * @since 6.0 + * @see #setDefaultTimeZone + */ + public void setDefaultTimeZoneFunction(Function defaultTimeZoneFunction) { + Assert.notNull(defaultTimeZoneFunction, "defaultTimeZoneFunction must not be null"); + this.defaultTimeZoneFunction = defaultTimeZoneFunction; + } @Override public Locale resolveLocale(HttpServletRequest request) { Locale locale = (Locale) WebUtils.getSessionAttribute(request, this.localeAttributeName); if (locale == null) { - locale = determineDefaultLocale(request); + locale = this.defaultLocaleFunction.apply(request); } return locale; } @@ -123,7 +163,7 @@ public LocaleContext resolveLocaleContext(final HttpServletRequest request) { public Locale getLocale() { Locale locale = (Locale) WebUtils.getSessionAttribute(request, localeAttributeName); if (locale == null) { - locale = determineDefaultLocale(request); + locale = SessionLocaleResolver.this.defaultLocaleFunction.apply(request); } return locale; } @@ -132,7 +172,7 @@ public Locale getLocale() { public TimeZone getTimeZone() { TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, timeZoneAttributeName); if (timeZone == null) { - timeZone = determineDefaultTimeZone(request); + timeZone = SessionLocaleResolver.this.defaultTimeZoneFunction.apply(request); } return timeZone; } @@ -165,13 +205,11 @@ public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletRe * @return the default locale (never {@code null}) * @see #setDefaultLocale * @see jakarta.servlet.http.HttpServletRequest#getLocale() + * @deprecated as of 6.0, in favor of {@link #setDefaultLocaleFunction(Function)} */ + @Deprecated protected Locale determineDefaultLocale(HttpServletRequest request) { - Locale defaultLocale = getDefaultLocale(); - if (defaultLocale == null) { - defaultLocale = request.getLocale(); - } - return defaultLocale; + return this.defaultLocaleFunction.apply(request); } /** @@ -182,10 +220,12 @@ protected Locale determineDefaultLocale(HttpServletRequest request) { * @param request the request to resolve the time zone for * @return the default time zone (or {@code null} if none defined) * @see #setDefaultTimeZone + * @deprecated as of 6.0, in favor of {@link #setDefaultTimeZoneFunction(Function)} */ + @Deprecated @Nullable protected TimeZone determineDefaultTimeZone(HttpServletRequest request) { - return getDefaultTimeZone(); + return this.defaultTimeZoneFunction.apply(request); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java index 70fb14d5e2a4..2a7c0bd97137 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java @@ -41,6 +41,7 @@ * @author Juergen Hoeller * @author Rick Evans * @author Sam Brannen + * @author Vedran Pavic */ class CookieLocaleResolverTests { @@ -410,4 +411,24 @@ void setLocaleContextToNullWithDefault() { assertThat(localeCookie.getValue()).isEqualTo(""); } + @Test + void testCustomDefaultLocaleFunction() { + request.addPreferredLocale(Locale.TAIWAN); + + resolver.setDefaultLocaleFunction(request -> Locale.GERMAN); + + assertThat(resolver.resolveLocale(request)).isEqualTo(Locale.GERMAN); + } + + @Test + void testCustomDefaultTimeZoneFunction() { + request.addPreferredLocale(Locale.TAIWAN); + + resolver.setDefaultTimeZoneFunction(request -> TimeZone.getTimeZone("GMT+1")); + + TimeZoneAwareLocaleContext context = (TimeZoneAwareLocaleContext) resolver.resolveLocaleContext(request); + assertThat(context.getLocale()).isEqualTo(Locale.TAIWAN); + assertThat(context.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT+1")); + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.java index 5b4af96191b0..8b78dfcfd625 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.java @@ -17,10 +17,12 @@ package org.springframework.web.servlet.i18n; import java.util.Locale; +import java.util.TimeZone; import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; +import org.springframework.context.i18n.TimeZoneAwareLocaleContext; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletResponse; @@ -31,6 +33,7 @@ * * @author Juergen Hoeller * @author Sam Brannen + * @author Vedran Pavic */ class SessionLocaleResolverTests { @@ -94,4 +97,25 @@ void setLocaleToNullLocale() throws Exception { assertThat(resolver.resolveLocale(request)).isEqualTo(Locale.TAIWAN); } + @Test + void testCustomDefaultLocaleFunction() { + request.addPreferredLocale(Locale.TAIWAN); + + SessionLocaleResolver resolver = new SessionLocaleResolver(); + resolver.setDefaultLocaleFunction(request -> Locale.GERMAN); + + assertThat(resolver.resolveLocale(request)).isEqualTo(Locale.GERMAN); + } + + @Test + void testCustomDefaultTimeZoneFunction() { + request.addPreferredLocale(Locale.TAIWAN); + + resolver.setDefaultTimeZoneFunction(request -> TimeZone.getTimeZone("GMT+1")); + + TimeZoneAwareLocaleContext context = (TimeZoneAwareLocaleContext) resolver.resolveLocaleContext(request); + assertThat(context.getLocale()).isEqualTo(Locale.TAIWAN); + assertThat(context.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT+1")); + } + }