From 864dcf67d8d62f905f87ad391727473829a25c6c Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Tue, 26 Oct 2021 00:31:56 +0200 Subject: [PATCH] Simplify default locale/timezone resolution in cookie/session locale resolvers At present, the customization of the default locale and timezone resolution in CookieLocaleResolver and SessionLocaleResolver requires subclassing them and overriding determineDefaultLocale and/or determineDefaultTimeZone methods. This commit simplifies resolution of the default locale and timezone resolution by introducing dedicated functions for these purposes, thus allowing customization without needing to resort to subclassing the locale resolvers. Closes gh-27609 --- .../servlet/i18n/CookieLocaleResolver.java | 64 +++++++++++++++---- .../servlet/i18n/SessionLocaleResolver.java | 58 ++++++++++++++--- .../i18n/CookieLocaleResolverTests.java | 21 ++++++ .../i18n/SessionLocaleResolverTests.java | 24 +++++++ 4 files changed, 146 insertions(+), 21 deletions(-) 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 48e870601b..b776c60db1 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 @@ package org.springframework.web.servlet.i18n; 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 @@ import org.springframework.web.util.WebUtils; * * @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 {@link CookieLocaleResolver} using the @@ -137,8 +148,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte * @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 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte 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 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte } 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 class CookieLocaleResolver extends CookieGenerator implements LocaleConte 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))); } @@ -335,13 +375,11 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte * @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); } /** @@ -352,10 +390,12 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte * @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 2b659333fc..45ae24649b 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 @@ -18,6 +18,7 @@ package org.springframework.web.servlet.i18n; 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 jakarta.servlet.http.HttpServletResponse; 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 @@ import org.springframework.web.util.WebUtils; * 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 class SessionLocaleResolver extends AbstractLocaleContextResolver { 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 class SessionLocaleResolver extends AbstractLocaleContextResolver { 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 class SessionLocaleResolver extends AbstractLocaleContextResolver { public TimeZone getTimeZone() { TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, timeZoneAttributeName); if (timeZone == null) { - timeZone = determineDefaultTimeZone(request); + timeZone = SessionLocaleResolver.this.defaultTimeZoneFunction.apply(request); } return timeZone; } @@ -167,13 +207,11 @@ public class SessionLocaleResolver extends AbstractLocaleContextResolver { * @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); } /** @@ -184,10 +222,12 @@ public class SessionLocaleResolver extends AbstractLocaleContextResolver { * @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 70fb14d5e2..2a7c0bd971 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 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * @author Juergen Hoeller * @author Rick Evans * @author Sam Brannen + * @author Vedran Pavic */ class CookieLocaleResolverTests { @@ -410,4 +411,24 @@ class CookieLocaleResolverTests { 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 5b4af96191..8b78dfcfd6 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 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Juergen Hoeller * @author Sam Brannen + * @author Vedran Pavic */ class SessionLocaleResolverTests { @@ -94,4 +97,25 @@ class SessionLocaleResolverTests { 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")); + } + }