Author: grobmeier Date: Mon Jul 15 10:06:07 2013 New Revision: 1503160 URL: http://svn.apache.org/r1503160 Log: WW-4141: Added support for saving locale in cookie
Added: struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java (with props) struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java (with props) Modified: struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java Added: struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java?rev=1503160&view=auto ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java (added) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java Mon Jul 15 10:06:07 2013 @@ -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; + +/** + * <!-- START SNIPPET: description --> + * This interceptor extends the original xwork i18n interceptor + * and adds functionality to support cookies. + * + * <!-- END SNIPPET: description --> + * + * <!-- START SNIPPET: parameters --> + * <p/> + * <ul> + * <p/> + * <li>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 <b>request_locale</b></li> + * <p/> + * <li>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 <b>request_cookie_locale</b></li> + * <p/> + * <li>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 <b>request_only_locale</b></li> + * <p/> + * <li>attributeName (optional) - the name of the session key to store the selected locale. By default this is + * <b>WW_TRANS_I18N_LOCALE</b></li> + * <p/> + * </ul> + * <p/> + * <!-- END SNIPPET: parameters --> + * + * <!-- START SNIPPET: example --> + * <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> + * <!-- END SNIPPET: example --> + */ +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<String, Object> 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<String, Object> 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; + } +} Propchange: struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java ------------------------------------------------------------------------------ svn:eol-style = native Added: struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java?rev=1503160&view=auto ============================================================================== --- struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java (added) +++ struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java Mon Jul 15 10:06:07 2013 @@ -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<String, Object> params; + private Map session; + + @Before + public void setUp() throws Exception { + interceptor = new I18nInterceptor(); + interceptor.init(); + params = new HashMap<String, Object>(); + session = new HashMap(); + + Map<String, Object> ctx = new HashMap<String, Object>(); + 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")); + } +} Propchange: struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java?rev=1503160&r1=1503159&r2=1503160&view=diff ============================================================================== --- struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java (original) +++ struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/I18nInterceptor.java Mon Jul 15 10:06:07 2013 @@ -97,6 +97,9 @@ public class I18nInterceptor extends Abs 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,50 +124,107 @@ public class I18nInterceptor extends Abs LOG.debug("intercept '#0/#1' {", invocation.getProxy().getNamespace(), invocation.getProxy().getActionName()); } - //get requested locale - Map<String, Object> params = invocation.getInvocationContext().getParameters(); - boolean storeInSession = true; + LocaleFinder localeFinder = new LocaleFinder(invocation); + Locale locale = getLocaleFromParam(localeFinder.getRequestedLocale()); + locale = storeLocale(invocation, locale, localeFinder.getStorage()); + saveLocale(invocation, locale); - Object requestedLocale = findLocaleParameter(params, parameterName); - if (requestedLocale == null) { - requestedLocale = findLocaleParameter(params, requestOnlyParameterName); - if (requestedLocale != null) { - storeInSession = false; - } + if (LOG.isDebugEnabled()) { + LOG.debug("before Locale=#0", invocation.getStack().findValue("locale")); } - Locale locale = getLocaleFromParam(requestedLocale); + final String result = invocation.invoke(); + if (LOG.isDebugEnabled()) { + LOG.debug("after Locale=#0", invocation.getStack().findValue("locale")); + LOG.debug("intercept } "); + } + + return result; + } + + /** + * Store the locale to the chosen storage, like f. e. the session + * + * @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 storeLocale(ActionInvocation invocation, Locale locale, String storage) { //save it in session Map<String, Object> session = invocation.getInvocationContext().getSession(); if (session != null) { synchronized (session) { if (locale == null) { - storeInSession = false; + storage = Storage.NONE.toString(); locale = readStoredLocale(invocation, session); } - if (storeInSession) { + if (Storage.SESSION.toString().equals(storage)) { session.put(attributeName, locale); } } } + return locale; + } - saveLocale(invocation, locale); + protected class LocaleFinder { + protected String storage = Storage.SESSION.toString(); + protected Object requestedLocale = null; - if (LOG.isDebugEnabled()) { - LOG.debug("before Locale=#0", invocation.getStack().findValue("locale")); + protected ActionInvocation actionInvocation = null; + + protected LocaleFinder(ActionInvocation invocation) { + actionInvocation = invocation; + find(); } - final String result = invocation.invoke(); - if (LOG.isDebugEnabled()) { - LOG.debug("after Locale=#0", invocation.getStack().findValue("locale")); - LOG.debug("intercept } "); + protected void find() { + //get requested locale + Map<String, Object> 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(); + } } - return result; + 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 + * + * @param requestedLocale the parameter from the request + * @return the Locale + */ + protected Locale getLocaleFromParam(Object requestedLocale) { + Locale locale = null; + if (requestedLocale != null) { + locale = (requestedLocale instanceof Locale) ? + (Locale) requestedLocale : + LocalizedTextUtil.localeFromString(requestedLocale.toString(), null); + if (locale != null && LOG.isDebugEnabled()) { + LOG.debug("applied request locale=#0", locale); + } + } + return locale; } /** @@ -176,7 +236,17 @@ public class I18nInterceptor extends Abs * @return the read locale */ protected Locale readStoredLocale(ActionInvocation invocation, Map<String, Object> session) { - // check session for saved locale + Locale locale = this.readStoredLocalFromSession(invocation, session); + + if (locale != null) { + return locale; + } + + return this.readStoredLocalFromCurrentInvocation(invocation); + } + + protected Locale readStoredLocalFromSession(ActionInvocation invocation, Map<String, Object> session) { + // check session for saved locale Object sessionLocale = session.get(attributeName); if (sessionLocale != null && sessionLocale instanceof Locale) { Locale locale = (Locale) sessionLocale; @@ -185,37 +255,19 @@ public class I18nInterceptor extends Abs } 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; - } - - /** - * Creates a Locale object from the request param, which might - * be already a Local or a String - * - * @param requestedLocale the parameter from the request - * @return the Locale - */ - protected Locale getLocaleFromParam(Object requestedLocale) { - Locale locale = null; - if (requestedLocale != null) { - locale = (requestedLocale instanceof Locale) ? - (Locale) requestedLocale : - LocalizedTextUtil.localeFromString(requestedLocale.toString(), null); - if (locale != null && LOG.isDebugEnabled()) { - LOG.debug("applied request locale=#0", locale); - } - } return locale; } - private Object findLocaleParameter(Map<String, Object> params, String parameterName) { + protected Object findLocaleParameter(Map<String, Object> params, String parameterName) { Object requestedLocale = params.remove(parameterName); if (requestedLocale != null && requestedLocale.getClass().isArray() && ((Object[]) requestedLocale).length == 1) {