You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2020/11/22 09:18:51 UTC

[struts] 01/01: WW-4929 Adds support for Accept-Language header when looking up Locale

This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-4929-support-accept-language
in repository https://gitbox.apache.org/repos/asf/struts.git

commit a1c52846888d555e94506f83e7eefb588f875392
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Sun Nov 22 10:18:41 2020 +0100

    WW-4929 Adds support for Accept-Language header when looking up Locale
---
 .../struts2/interceptor/I18nInterceptor.java       | 70 ++++++++++++++++++----
 .../struts2/interceptor/I18nInterceptorTest.java   | 43 +++++++++++--
 2 files changed, 96 insertions(+), 17 deletions(-)

diff --git a/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
index 4ddcedf..5964693 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
@@ -23,6 +23,7 @@ import com.opensymphony.xwork2.LocaleProvider;
 import com.opensymphony.xwork2.LocaleProviderFactory;
 import com.opensymphony.xwork2.inject.Inject;
 import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
+import com.opensymphony.xwork2.util.TextParseUtil;
 import org.apache.commons.lang3.LocaleUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -35,8 +36,12 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import java.util.Collections;
+import java.util.Enumeration;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * An interceptor that handles setting the locale specified in a session as the locale for the current action request.
@@ -59,8 +64,9 @@ public class I18nInterceptor extends AbstractInterceptor {
 
     protected LocaleProviderFactory localeProviderFactory;
 
-    // Request-Only = None
-    protected enum Storage { COOKIE, SESSION, NONE }
+    private Set<Locale> supportedLocale = Collections.emptySet();
+
+    protected enum Storage { COOKIE, SESSION, REQUEST, ACCEPT_LANGUAGE }
 
     public void setParameterName(String parameterName) {
         this.parameterName = parameterName;
@@ -80,7 +86,7 @@ public class I18nInterceptor extends AbstractInterceptor {
 
     public void setLocaleStorage(String storageName) {
         if (storageName == null || "".equals(storageName)) {
-            this.storage = Storage.NONE;
+            this.storage = Storage.ACCEPT_LANGUAGE;
         } else {
             try {
                 this.storage = Storage.valueOf(storageName.toUpperCase());
@@ -91,6 +97,19 @@ public class I18nInterceptor extends AbstractInterceptor {
         }
     }
 
+    /**
+     * Sets supported Locales by the application
+     *
+     * @param supportedLocale a comma separated list of supported Locale
+     */
+    public void setSupportedLocale(String supportedLocale) {
+        this.supportedLocale = TextParseUtil
+            .commaDelimitedStringToSet(supportedLocale)
+            .stream()
+            .map(Locale::new)
+            .collect(Collectors.toSet());
+    }
+
     @Inject
     public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) {
         this.localeProviderFactory = localeProviderFactory;
@@ -139,8 +158,10 @@ public class I18nInterceptor extends AbstractInterceptor {
             localeHandler = new CookieLocaleHandler(invocation);
         } else if (this.storage == Storage.SESSION) {
             localeHandler = new SessionLocaleHandler(invocation);
+        } else if (this.storage == Storage.REQUEST) {
+            localeHandler = new RequestLocaleHandler(invocation);
         } else {
-            localeHandler = new RequestOnlyLocaleHandler(invocation);
+            localeHandler = new AcceptLanguageHandler(invocation);
         }
 
         LOG.debug("Using LocaleFinder implementation {}", localeHandler.getClass().getName());
@@ -176,7 +197,7 @@ public class I18nInterceptor extends AbstractInterceptor {
 
         if (locale != null && !localeProvider.isValidLocale(locale)) {
             Locale defaultLocale = localeProvider.getLocale();
-            LOG.debug("Provided locale {} isn't valid, fallback to default locale", locale, defaultLocale);
+            LOG.debug("Provided locale {} isn't valid, fallback to default locale {}", locale, defaultLocale);
             locale = defaultLocale;
         }
 
@@ -200,7 +221,7 @@ public class I18nInterceptor extends AbstractInterceptor {
      * @param locale     The locale to save.
      */
     protected void useLocale(ActionInvocation invocation, Locale locale) {
-        invocation.getInvocationContext().setLocale(locale);
+        invocation.getInvocationContext().withLocale(locale);
     }
 
     /**
@@ -213,12 +234,12 @@ public class I18nInterceptor extends AbstractInterceptor {
         boolean shouldStore();
     }
 
-    protected class RequestOnlyLocaleHandler implements LocaleHandler {
+    protected class RequestLocaleHandler implements LocaleHandler {
 
-        protected ActionInvocation actionInvocation = null;
+        protected ActionInvocation actionInvocation;
         protected boolean shouldStore = true;
 
-        protected RequestOnlyLocaleHandler(ActionInvocation invocation) {
+        protected RequestLocaleHandler(ActionInvocation invocation) {
             actionInvocation = invocation;
         }
 
@@ -255,7 +276,32 @@ public class I18nInterceptor extends AbstractInterceptor {
         }
     }
 
-    protected class SessionLocaleHandler extends RequestOnlyLocaleHandler {
+    protected class AcceptLanguageHandler extends RequestLocaleHandler {
+
+        protected AcceptLanguageHandler(ActionInvocation invocation) {
+            super(invocation);
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        public Locale find() {
+            Enumeration locales = actionInvocation.getInvocationContext().getServletRequest().getLocales();
+            while (locales.hasMoreElements()) {
+                Locale locale = (Locale) locales.nextElement();
+                if (supportedLocale.contains(locale)) {
+                    return locale;
+                }
+            }
+            return super.find();
+        }
+
+        @Override
+        public boolean shouldStore() {
+            return false;
+        }
+    }
+
+    protected class SessionLocaleHandler extends RequestLocaleHandler {
 
         protected SessionLocaleHandler(ActionInvocation invocation) {
             super(invocation);
@@ -305,7 +351,7 @@ public class I18nInterceptor extends AbstractInterceptor {
                 String sessionId = session.getId();
                 synchronized (sessionId.intern()) {
                     Object sessionLocale = invocation.getInvocationContext().getSession().get(attributeName);
-                    if (sessionLocale != null && sessionLocale instanceof Locale) {
+                    if (sessionLocale instanceof Locale) {
                         locale = (Locale) sessionLocale;
                         LOG.debug("Applied session locale: {}", locale);
                     }
@@ -324,7 +370,7 @@ public class I18nInterceptor extends AbstractInterceptor {
         }
     }
 
-    protected class CookieLocaleHandler extends RequestOnlyLocaleHandler {
+    protected class CookieLocaleHandler extends RequestLocaleHandler {
         protected CookieLocaleHandler(ActionInvocation invocation) {
             super(invocation);
         }
diff --git a/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
index 99b9ffb..e27ad43 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
@@ -36,6 +36,7 @@ import org.springframework.mock.web.MockHttpSession;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletResponse;
 import java.io.Serializable;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -224,7 +225,6 @@ public class I18nInterceptorTest extends TestCase {
     }
 
     public void testCookieCreation() throws Exception {
-
         prepare(I18nInterceptor.DEFAULT_COOKIE_PARAMETER, "da_DK");
 
         final Cookie cookie = new Cookie(I18nInterceptor.DEFAULT_COOKIE_ATTRIBUTE, "da_DK");
@@ -243,6 +243,37 @@ public class I18nInterceptorTest extends TestCase {
         assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should not create a locale object
     }
 
+    public void testAcceptLanguageBasedLocale() throws Exception {
+        // given
+        request.setPreferredLocales(Arrays.asList(new Locale("da_DK"), new Locale("pl")));
+        interceptor.setLocaleStorage(null);
+        interceptor.setSupportedLocale("en,pl");
+
+        // when
+        interceptor.intercept(mai);
+
+        // then
+        assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should not be stored here
+        assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should not create a locale object
+        assertEquals(new Locale("pl"), mai.getInvocationContext().getLocale());
+    }
+
+    public void testAcceptLanguageBasedLocaleWithFallbackToDefault() throws Exception {
+        // given
+        request.setPreferredLocales(Arrays.asList(new Locale("da_DK"), new Locale("es")));
+
+        interceptor.setLocaleStorage(null);
+        interceptor.setSupportedLocale("en,pl");
+
+        // when
+        interceptor.intercept(mai);
+
+        // then
+        assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should not be stored here
+        assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should not create a locale object
+        assertEquals(Locale.US, mai.getInvocationContext().getLocale());
+    }
+
     private void prepare(String key, Serializable value) {
         Map<String, Serializable> params = new HashMap<>();
         params.put(key, value);
@@ -256,9 +287,10 @@ public class I18nInterceptorTest extends TestCase {
         interceptor.init();
         session = new HashMap<>();
 
-        ac = ActionContext.of(new HashMap<>()).bind();
-        ac.setSession(session);
-        ac.setParameters(HttpParameters.create().build());
+        ac = ActionContext.of(new HashMap<>())
+            .bind()
+            .withSession(session)
+            .withParameters(HttpParameters.create().build());
 
         request = new MockHttpServletRequest();
         request.setSession(new MockHttpSession());
@@ -286,7 +318,8 @@ public class I18nInterceptorTest extends TestCase {
     }
 
     static class CookieMatcher implements IArgumentMatcher {
-        private Cookie expected;
+
+        private final Cookie expected;
 
         CookieMatcher(Cookie cookie) {
             expected = cookie;