diff --git a/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
new file mode 100644
index 000000000..d2632c508
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
@@ -0,0 +1,176 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.struts2.interceptor;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.StrutsStatics;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ *
+ * This interceptor extends the original xwork i18n interceptor
+ * and adds functionality to support cookies.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * - parameterName (optional) - the name of the HTTP request parameter that dictates the locale to switch to and save
+ * in the session. By default this is request_locale
+ *
+ * - requestCookieParameterName (optional) - the name of the HTTP request parameter that dictates the locale to switch to
+ * and save in a cookien. By default this is request_cookie_locale
+ *
+ * - requestOnlyParameterName (optional) - the name of the HTTP request parameter that dictates the locale to switch to
+ * for the current request only, without saving it in the session. By default this is request_only_locale
+ *
+ * - attributeName (optional) - the name of the session key to store the selected locale. By default this is
+ * WW_TRANS_I18N_LOCALE
+ *
+ *
+ *
+ *
+ *
+ *
+ * <interceptor name="i18nCookie" class="org.apache.struts2.interceptor.I18nInterceptor"/>
+ *
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="i18nCookie"/>
+ * <interceptor-ref name="basicStack"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ */
+public class I18nInterceptor extends com.opensymphony.xwork2.interceptor.I18nInterceptor {
+ private static final long serialVersionUID = 4587460933182760358L;
+
+ public static final String DEFAULT_COOKIE_ATTRIBUTE = DEFAULT_SESSION_ATTRIBUTE;
+
+ public static final String COOKIE_STORAGE = "cookie";
+
+ public static final String DEFAULT_COOKIE_PARAMETER = "request_cookie_locale";
+ protected String requestCookieParameterName = DEFAULT_COOKIE_PARAMETER;
+
+ protected class CookieLocaleFinder extends LocaleFinder {
+ protected CookieLocaleFinder(ActionInvocation invocation) {
+ super(invocation);
+ }
+
+ @Override
+ protected void find() {
+ //get requested locale
+ Map params = actionInvocation.getInvocationContext().getParameters();
+ storage = Storage.SESSION.toString();
+
+ requestedLocale = findLocaleParameter(params, parameterName);
+
+ if (requestedLocale != null) {
+ return;
+ }
+
+ requestedLocale = findLocaleParameter(params, requestCookieParameterName);
+ if (requestedLocale != null) {
+ storage = COOKIE_STORAGE;
+ return;
+ }
+
+ requestedLocale = findLocaleParameter(params, requestOnlyParameterName);
+ if (requestedLocale != null) {
+ storage = Storage.NONE.toString();
+ }
+
+ }
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("intercept '#0/#1' {",
+ invocation.getProxy().getNamespace(), invocation.getProxy().getActionName());
+ }
+
+ LocaleFinder localeFinder = new CookieLocaleFinder(invocation);
+ Locale locale = getLocaleFromParam(localeFinder.getRequestedLocale());
+ locale = storeLocale(invocation, locale, localeFinder.getStorage());
+ saveLocale(invocation, locale);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("before Locale=#0", invocation.getStack().findValue("locale"));
+ }
+
+ final String result = invocation.invoke();
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("after Locale=#0", invocation.getStack().findValue("locale"));
+ LOG.debug("intercept } ");
+ }
+
+ return result;
+ }
+
+ @Override
+ protected Locale storeLocale(ActionInvocation invocation, Locale locale, String storage) {
+ if (COOKIE_STORAGE.equals(storage)) {
+ ActionContext ac = invocation.getInvocationContext();
+ HttpServletResponse response = (HttpServletResponse) ac.get(StrutsStatics.HTTP_RESPONSE);
+
+ Cookie cookie = new Cookie(DEFAULT_COOKIE_ATTRIBUTE, locale.toString());
+ cookie.setMaxAge(1209600); // two weeks
+ response.addCookie(cookie);
+
+ storage = Storage.SESSION.toString();
+ }
+
+ return super.storeLocale(invocation, locale, storage);
+ }
+
+ @Override
+ protected Locale readStoredLocale(ActionInvocation invocation, Map session) {
+ Locale locale = this.readStoredLocalFromSession(invocation, session);
+
+ if (locale != null) {
+ return locale;
+ }
+
+ Cookie[] cookies = ServletActionContext.getRequest().getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if (DEFAULT_COOKIE_ATTRIBUTE.equals(cookie.getName())) {
+ return getLocaleFromParam(cookie.getValue());
+ }
+ }
+ }
+
+ return this.readStoredLocalFromCurrentInvocation(invocation);
+ }
+
+ public void setRequestCookieParameterName(String requestCookieParameterName) {
+ this.requestCookieParameterName = requestCookieParameterName;
+ }
+}
diff --git a/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
new file mode 100644
index 000000000..04d853e5f
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
@@ -0,0 +1,264 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.struts2.interceptor;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+import org.apache.struts2.StrutsStatics;
+import org.easymock.EasyMock;
+import org.easymock.IArgumentMatcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.jmock.expectation.AssertMo.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class I18nInterceptorTest {
+ private I18nInterceptor interceptor;
+ private ActionInvocation mai;
+ private ActionContext ac;
+ private Map params;
+ private Map session;
+
+ @Before
+ public void setUp() throws Exception {
+ interceptor = new I18nInterceptor();
+ interceptor.init();
+ params = new HashMap();
+ session = new HashMap();
+
+ Map ctx = new HashMap();
+ ctx.put(ActionContext.PARAMETERS, params);
+ ctx.put(ActionContext.SESSION, session);
+ ac = new ActionContext(ctx);
+
+ Action action = new Action() {
+ public String execute() throws Exception {
+ return SUCCESS;
+ }
+ };
+ mai = new MockActionInvocation();
+ ((MockActionInvocation) mai).setAction(action);
+ ((MockActionInvocation) mai).setInvocationContext(ac);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ interceptor.destroy();
+ interceptor = null;
+ ac = null;
+ params = null;
+ session = null;
+ mai = null;
+ }
+
+ static class CookieMatcher implements IArgumentMatcher {
+ private Cookie expected;
+
+ CookieMatcher(Cookie cookie) {
+ expected = cookie;
+ }
+
+ public boolean matches(Object argument) {
+ Cookie cookie = ((Cookie) argument);
+ return
+ (cookie.getName().equals(expected.getName()) &&
+ cookie.getValue().equals(expected.getValue()));
+ }
+
+ public static Cookie eqCookie(Cookie ck) {
+ EasyMock.reportMatcher(new CookieMatcher(ck));
+ return null;
+ }
+
+ public void appendTo(StringBuffer buffer) {
+ buffer
+ .append("Received")
+ .append(expected.getName())
+ .append("/")
+ .append(expected.getValue());
+ }
+ }
+
+ @Test
+ public void testCookieCreation() throws Exception {
+
+ params.put(I18nInterceptor.DEFAULT_COOKIE_PARAMETER, "da_DK");
+
+ final Cookie cookie = new Cookie(I18nInterceptor.DEFAULT_COOKIE_ATTRIBUTE, "da_DK");
+
+ HttpServletResponse response = EasyMock.createMock(HttpServletResponse.class);
+ response.addCookie(CookieMatcher.eqCookie(cookie));
+ EasyMock.replay(response);
+
+ ac.put(StrutsStatics.HTTP_RESPONSE, response);
+ interceptor.intercept(mai);
+
+ EasyMock.verify(response);
+
+ Locale denmark = new Locale("da", "DK");
+ assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+ }
+
+ @Test
+ public void testNoSession() throws Exception {
+ ac.setSession(null);
+ interceptor.intercept(mai);
+ }
+
+ @Test
+ public void testDefaultLocale() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_PARAMETER, "_"); // bad locale that would get us default locale instead
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(Locale.getDefault(), session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+ }
+
+ @Test
+ public void testDenmarkLocale() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_PARAMETER, "da_DK");
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ Locale denmark = new Locale("da", "DK");
+ assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+ }
+
+ @Test
+ public void testDenmarkLocaleRequestOnly() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_REQUESTONLY_PARAMETER, "da_DK");
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ Locale denmark = new Locale("da", "DK");
+ assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(denmark, mai.getInvocationContext().getLocale()); // should create a locale object
+ }
+
+ @Test
+ public void testCountryOnlyLocale() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_PARAMETER, "DK");
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ Locale denmark = new Locale("DK");
+ assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+ }
+
+ @Test
+ public void testLanguageOnlyLocale() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_PARAMETER, "da_");
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ Locale denmark = new Locale("da");
+ assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+ }
+
+ @Test
+ public void testWithVariant() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_PARAMETER, "fr_CA_xx");
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ Locale variant = new Locale("fr", "CA", "xx");
+ Locale locale = (Locale) session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE);
+ assertNotNull(locale); // should be stored here
+ assertEquals(variant, locale);
+ assertEquals("xx", locale.getVariant());
+ }
+
+ @Test
+ public void testWithVariantRequestOnly() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_REQUESTONLY_PARAMETER, "fr_CA_xx");
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+ assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE));
+
+ Locale variant = new Locale("fr", "CA", "xx");
+ Locale locale = mai.getInvocationContext().getLocale();
+ assertNotNull(locale); // should be stored here
+ assertEquals(variant, locale);
+ assertEquals("xx", locale.getVariant());
+ }
+
+ @Test
+ public void testRealLocaleObjectInParams() throws Exception {
+ params.put(I18nInterceptor.DEFAULT_PARAMETER, Locale.CANADA_FRENCH);
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(Locale.CANADA_FRENCH, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+ }
+
+ @Test
+ public void testRealLocalesInParams() throws Exception {
+ Locale[] locales = new Locale[]{Locale.CANADA_FRENCH};
+ assertTrue(locales.getClass().isArray());
+ params.put(I18nInterceptor.DEFAULT_PARAMETER, locales);
+ interceptor.intercept(mai);
+
+ assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
+
+ assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+ assertEquals(Locale.CANADA_FRENCH, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE));
+ }
+
+ @Test
+ public void testSetParameterAndAttributeNames() throws Exception {
+ interceptor.setAttributeName("hello");
+ interceptor.setParameterName("world");
+
+ params.put("world", Locale.CHINA);
+ interceptor.intercept(mai);
+
+ assertNull(params.get("world")); // should have been removed
+
+ assertNotNull(session.get("hello")); // should be stored here
+ assertEquals(Locale.CHINA, session.get("hello"));
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java
index f2088d359..ad3fd36ac 100644
--- a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java
@@ -97,6 +97,9 @@ public class I18nInterceptor extends AbstractInterceptor {
protected String requestOnlyParameterName = DEFAULT_REQUESTONLY_PARAMETER;
protected String attributeName = DEFAULT_SESSION_ATTRIBUTE;
+ // Request-Only = None
+ protected enum Storage { SESSION, NONE }
+
public I18nInterceptor() {
if (LOG.isDebugEnabled()) {
LOG.debug("new I18nInterceptor()");
@@ -121,37 +124,10 @@ public class I18nInterceptor extends AbstractInterceptor {
LOG.debug("intercept '#0/#1' {",
invocation.getProxy().getNamespace(), invocation.getProxy().getActionName());
}
- //get requested locale
- Map params = invocation.getInvocationContext().getParameters();
-
- boolean storeInSession = true;
-
- Object requestedLocale = findLocaleParameter(params, parameterName);
- if (requestedLocale == null) {
- requestedLocale = findLocaleParameter(params, requestOnlyParameterName);
- if (requestedLocale != null) {
- storeInSession = false;
- }
- }
-
- Locale locale = getLocaleFromParam(requestedLocale);
-
- //save it in session
- Map session = invocation.getInvocationContext().getSession();
-
- if (session != null) {
- synchronized (session) {
- if (locale == null) {
- storeInSession = false;
- locale = readStoredLocale(invocation, session);
- }
-
- if (storeInSession) {
- session.put(attributeName, locale);
- }
- }
- }
+ LocaleFinder localeFinder = new LocaleFinder(invocation);
+ Locale locale = getLocaleFromParam(localeFinder.getRequestedLocale());
+ locale = storeLocale(invocation, locale, localeFinder.getStorage());
saveLocale(invocation, locale);
if (LOG.isDebugEnabled()) {
@@ -159,6 +135,7 @@ public class I18nInterceptor extends AbstractInterceptor {
}
final String result = invocation.invoke();
+
if (LOG.isDebugEnabled()) {
LOG.debug("after Locale=#0", invocation.getStack().findValue("locale"));
LOG.debug("intercept } ");
@@ -168,33 +145,68 @@ public class I18nInterceptor extends AbstractInterceptor {
}
/**
- * Reads the locale from the session, and if not found from the
- * current invocation (=browser)
+ * Store the locale to the chosen storage, like f. e. the session
*
- * @param invocation the current invocation
- * @param session the current session
- * @return the read locale
+ * @param invocation the action invocation
+ * @param locale the locale to store
+ * @param storage the place to store this locale (like Storage.SESSSION.toString())
*/
- protected Locale readStoredLocale(ActionInvocation invocation, Map session) {
- // check session for saved locale
- Object sessionLocale = session.get(attributeName);
- if (sessionLocale != null && sessionLocale instanceof Locale) {
- Locale locale = (Locale) sessionLocale;
- if (LOG.isDebugEnabled()) {
- LOG.debug("applied session locale=#0", locale);
+ protected Locale storeLocale(ActionInvocation invocation, Locale locale, String storage) {
+ //save it in session
+ Map session = invocation.getInvocationContext().getSession();
+
+ if (session != null) {
+ synchronized (session) {
+ if (locale == null) {
+ storage = Storage.NONE.toString();
+ locale = readStoredLocale(invocation, session);
+ }
+
+ if (Storage.SESSION.toString().equals(storage)) {
+ session.put(attributeName, locale);
+ }
}
- return locale;
}
-
- // no overriding locale definition found, stay with current invocation (=browser) locale
- Locale locale = invocation.getInvocationContext().getLocale();
- if (locale != null && LOG.isDebugEnabled()) {
- LOG.debug("applied invocation context locale=#0", locale);
- }
-
return locale;
}
+ protected class LocaleFinder {
+ protected String storage = Storage.SESSION.toString();
+ protected Object requestedLocale = null;
+
+ protected ActionInvocation actionInvocation = null;
+
+ protected LocaleFinder(ActionInvocation invocation) {
+ actionInvocation = invocation;
+ find();
+ }
+
+ protected void find() {
+ //get requested locale
+ Map params = actionInvocation.getInvocationContext().getParameters();
+
+ storage = Storage.SESSION.toString();
+
+ requestedLocale = findLocaleParameter(params, parameterName);
+ if (requestedLocale != null) {
+ return;
+ }
+
+ requestedLocale = findLocaleParameter(params, requestOnlyParameterName);
+ if (requestedLocale != null) {
+ storage = Storage.NONE.toString();
+ }
+ }
+
+ public String getStorage() {
+ return storage;
+ }
+
+ public Object getRequestedLocale() {
+ return requestedLocale;
+ }
+ }
+
/**
* Creates a Locale object from the request param, which might
* be already a Local or a String
@@ -215,7 +227,47 @@ public class I18nInterceptor extends AbstractInterceptor {
return locale;
}
- private Object findLocaleParameter(Map params, String parameterName) {
+ /**
+ * Reads the locale from the session, and if not found from the
+ * current invocation (=browser)
+ *
+ * @param invocation the current invocation
+ * @param session the current session
+ * @return the read locale
+ */
+ protected Locale readStoredLocale(ActionInvocation invocation, Map session) {
+ Locale locale = this.readStoredLocalFromSession(invocation, session);
+
+ if (locale != null) {
+ return locale;
+ }
+
+ return this.readStoredLocalFromCurrentInvocation(invocation);
+ }
+
+ protected Locale readStoredLocalFromSession(ActionInvocation invocation, Map session) {
+ // check session for saved locale
+ Object sessionLocale = session.get(attributeName);
+ if (sessionLocale != null && sessionLocale instanceof Locale) {
+ Locale locale = (Locale) sessionLocale;
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("applied session locale=#0", locale);
+ }
+ return locale;
+ }
+ return null;
+ }
+
+ protected Locale readStoredLocalFromCurrentInvocation(ActionInvocation invocation) {
+ // no overriding locale definition found, stay with current invocation (=browser) locale
+ Locale locale = invocation.getInvocationContext().getLocale();
+ if (locale != null && LOG.isDebugEnabled()) {
+ LOG.debug("applied invocation context locale=#0", locale);
+ }
+ return locale;
+ }
+
+ protected Object findLocaleParameter(Map params, String parameterName) {
Object requestedLocale = params.remove(parameterName);
if (requestedLocale != null && requestedLocale.getClass().isArray()
&& ((Object[]) requestedLocale).length == 1) {