This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5670 in repository https://gitbox.apache.org/repos/asf/struts.git
commit 599facaf7938d78abfb570a5ecd81b709818576e Author: Lukasz Lenart <[email protected]> AuthorDate: Sun Sep 13 11:25:55 2026 +0200 WW-5670 fix(core): parse struts.locale once and stop mislabelling the fallback locale When struts.locale was unparseable, Dispatcher.getLocale logged "defaulting to request locale [X]" even though, with struts.locale.validateRequestLocale enabled, resolveRequestLocale may have rejected the request locale and returned the JVM default. It also re-parsed the value on every request (twice on that path) although the result never changes after injection. Parse struts.locale once in setDefaultLocale, warn once there with the parse failure, and have both getLocale and resolveRequestLocale read the parsed value. The per-request WARN now says "defaulting to resolved locale [X]". No change to which locale is resolved. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- .../org/apache/struts2/dispatcher/Dispatcher.java | 42 ++++++++++------- .../apache/struts2/dispatcher/DispatcherTest.java | 54 ++++++++++++++++++++++ 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java index 5b7d90556..d249121f5 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java @@ -151,6 +151,11 @@ public class Dispatcher { */ private String defaultLocale; + /** + * {@link #defaultLocale} parsed once at injection time; {@code null} when unset or unparseable. + */ + private Locale parsedDefaultLocale; + /** * Store state of {@link StrutsConstants#STRUTS_LOCALE_VALIDATE_REQUEST} setting. */ @@ -312,6 +317,15 @@ public class Dispatcher { */ @Inject(value = StrutsConstants.STRUTS_LOCALE, required = false) public void setDefaultLocale(String val) { + Locale parsed = null; + if (val != null) { + try { + parsed = LocaleUtils.toLocale(val); + } catch (IllegalArgumentException e) { + LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, the locale will be resolved per request instead", val), e); + } + } + parsedDefaultLocale = parsed; defaultLocale = val; } @@ -938,20 +952,18 @@ public class Dispatcher { } protected Locale getLocale(HttpServletRequest request) { + if (parsedDefaultLocale != null) { + return parsedDefaultLocale; + } Locale locale; if (defaultLocale != null) { try { - locale = LocaleUtils.toLocale(defaultLocale); - } catch (IllegalArgumentException e) { - try { - locale = resolveRequestLocale(request); - LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, defaulting to request locale [{}]", - defaultLocale, locale), e); - } catch (RuntimeException rex) { - LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, and cannot get locale from HTTP Request, falling back to system default locale", - defaultLocale), rex); - locale = Locale.getDefault(); - } + locale = resolveRequestLocale(request); + LOG.warn("Cannot convert 'struts.locale' = [{}] to proper locale, defaulting to resolved locale [{}]", defaultLocale, locale); + } catch (RuntimeException rex) { + LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, and cannot get locale from HTTP Request, falling back to system default locale", + defaultLocale), rex); + locale = Locale.getDefault(); } } else { try { @@ -975,12 +987,8 @@ public class Dispatcher { if (!validateRequestLocale || LocaleUtils.isAvailableLocale(locale)) { return locale; } - if (defaultLocale != null) { - try { - return LocaleUtils.toLocale(defaultLocale); - } catch (IllegalArgumentException e) { - LOG.debug("Configured 'struts.locale' = [{}] is not parseable; falling back to system default", defaultLocale); - } + if (parsedDefaultLocale != null) { + return parsedDefaultLocale; } LOG.debug("Request locale [{}] is not available; falling back to system default locale", locale); return Locale.getDefault(); diff --git a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java index ab1191cbe..970863769 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java @@ -18,6 +18,12 @@ */ package org.apache.struts2.dispatcher; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; import org.apache.struts2.ActionContext; import org.apache.struts2.text.LocalizedTextProvider; import org.apache.struts2.ObjectFactory; @@ -50,8 +56,10 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -611,6 +619,52 @@ public class DispatcherTest extends StrutsJUnit4InternalTestCase { Locale.getDefault(), dispatcher.getLocale(request)); } + @Test + public void testGetLocale_With_BadDefaultLocale_ValidateOn_UnavailableRequestLocale() { + Map<String, String> params = new HashMap<>(); + params.put(StrutsConstants.STRUTS_LOCALE, "This_is_not_a_valid_Locale_string"); + params.put(StrutsConstants.STRUTS_LOCALE_VALIDATE_REQUEST, "true"); + initDispatcher(params); + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getLocale()).thenReturn(new Locale("en", "US", "xzz99")); + + assertEquals("Unparseable struts.locale and rejected request locale must fall back to system default", + Locale.getDefault(), dispatcher.getLocale(request)); + } + + @Test + public void testGetLocale_With_BadDefaultLocale_WarnsWithResolvedLocaleNotRequestLocale() { + Map<String, String> params = new HashMap<>(); + params.put(StrutsConstants.STRUTS_LOCALE, "This_is_not_a_valid_Locale_string"); + params.put(StrutsConstants.STRUTS_LOCALE_VALIDATE_REQUEST, "true"); + initDispatcher(params); + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getLocale()).thenReturn(new Locale("en", "US", "xzz99")); + + List<String> warnings = new ArrayList<>(); + Logger logger = (Logger) LogManager.getLogger(Dispatcher.class); + AbstractAppender appender = new AbstractAppender("WW-5670", null, null, false, Property.EMPTY_ARRAY) { + @Override + public void append(LogEvent event) { + if (event.getLevel() == Level.WARN) { + warnings.add(event.getMessage().getFormattedMessage()); + } + } + }; + appender.start(); + logger.addAppender(appender); + try { + dispatcher.getLocale(request); + } finally { + logger.removeAppender(appender); + appender.stop(); + } + + assertThat(warnings).singleElement().asString() + .contains("defaulting to resolved locale [" + Locale.getDefault() + "]") + .doesNotContain("request locale"); + } + @Test public void dispatcherReinjectedAfterReload() { HttpServletRequest request = mock(HttpServletRequest.class);
