Merge pull request #451 from apache/WW-4929-support-accept-language

[WW-4929] Adds support for Accept-Language header when looking up Locale
This commit is contained in:
Lukasz Lenart
2020-11-28 10:16:59 +01:00
committed by GitHub
2 changed files with 96 additions and 17 deletions
@@ -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);
}
@@ -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;