blob: 4ddcedfce8b24102f83c7df7dd96054c892b6d31 [file] [log] [blame]
/*
* 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.ActionInvocation;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.Parameter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Locale;
import java.util.Map;
/**
* An interceptor that handles setting the locale specified in a session as the locale for the current action request.
*/
public class I18nInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(I18nInterceptor.class);
public static final String DEFAULT_SESSION_ATTRIBUTE = "WW_TRANS_I18N_LOCALE";
public static final String DEFAULT_PARAMETER = "request_locale";
public static final String DEFAULT_REQUEST_ONLY_PARAMETER = "request_only_locale";
public static final String DEFAULT_COOKIE_ATTRIBUTE = DEFAULT_SESSION_ATTRIBUTE;
public static final String DEFAULT_COOKIE_PARAMETER = "request_cookie_locale";
protected String parameterName = DEFAULT_PARAMETER;
protected String requestOnlyParameterName = DEFAULT_REQUEST_ONLY_PARAMETER;
protected String attributeName = DEFAULT_SESSION_ATTRIBUTE;
protected String requestCookieParameterName = DEFAULT_COOKIE_PARAMETER;
protected Storage storage = Storage.SESSION;
protected LocaleProviderFactory localeProviderFactory;
// Request-Only = None
protected enum Storage { COOKIE, SESSION, NONE }
public void setParameterName(String parameterName) {
this.parameterName = parameterName;
}
public void setAttributeName(String attributeName) {
this.attributeName = attributeName;
}
public void setRequestOnlyParameterName(String requestOnlyParameterName) {
this.requestOnlyParameterName = requestOnlyParameterName;
}
public void setRequestCookieParameterName(String requestCookieParameterName) {
this.requestCookieParameterName = requestCookieParameterName;
}
public void setLocaleStorage(String storageName) {
if (storageName == null || "".equals(storageName)) {
this.storage = Storage.NONE;
} else {
try {
this.storage = Storage.valueOf(storageName.toUpperCase());
} catch (IllegalArgumentException e) {
LOG.warn(new ParameterizedMessage("Wrong storage name [{}] was defined, falling back to {}", storageName, Storage.SESSION), e);
this.storage = Storage.SESSION;
}
}
}
@Inject
public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) {
this.localeProviderFactory = localeProviderFactory;
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
LOG.debug("Intercept '{}/{}'", invocation.getProxy().getNamespace(), invocation.getProxy().getActionName());
LocaleHandler localeHandler = getLocaleHandler(invocation);
Locale locale = localeHandler.find();
if (locale == null) {
locale = localeHandler.read(invocation);
}
if (localeHandler.shouldStore()) {
locale = localeHandler.store(invocation, locale);
}
useLocale(invocation, locale);
if (LOG.isDebugEnabled()) {
LOG.debug("Before action invocation Locale={}", invocation.getStack().findValue("locale"));
}
try {
return invocation.invoke();
} finally {
if (LOG.isDebugEnabled()) {
LOG.debug("After action invocation Locale={}", invocation.getStack().findValue("locale"));
}
}
}
/**
* Override this method to use your own implementation of {@link LocaleHandler}
*
* @param invocation current action invocation context
* @return instance of {@link LocaleHandler}
*/
protected LocaleHandler getLocaleHandler(ActionInvocation invocation) {
LocaleHandler localeHandler;
if (this.storage == Storage.COOKIE) {
localeHandler = new CookieLocaleHandler(invocation);
} else if (this.storage == Storage.SESSION) {
localeHandler = new SessionLocaleHandler(invocation);
} else {
localeHandler = new RequestOnlyLocaleHandler(invocation);
}
LOG.debug("Using LocaleFinder implementation {}", localeHandler.getClass().getName());
return localeHandler;
}
/**
* 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) {
LocaleProvider localeProvider = localeProviderFactory.createLocaleProvider();
Locale locale = null;
if (requestedLocale != null) {
if (requestedLocale instanceof Locale) {
locale = (Locale) requestedLocale;
} else {
String localeStr = requestedLocale.toString();
if (localeProvider.isValidLocaleString(localeStr)) {
locale = LocaleUtils.toLocale(localeStr);
} else {
locale = localeProvider.getLocale();
}
}
if (locale != null) {
LOG.debug("Found locale: {}", locale);
}
}
if (locale != null && !localeProvider.isValidLocale(locale)) {
Locale defaultLocale = localeProvider.getLocale();
LOG.debug("Provided locale {} isn't valid, fallback to default locale", locale, defaultLocale);
locale = defaultLocale;
}
return locale;
}
protected Parameter findLocaleParameter(ActionInvocation invocation, String parameterName) {
HttpParameters params = invocation.getInvocationContext().getParameters();
Parameter requestedLocale = params.get(parameterName);
params.remove(parameterName);
if (requestedLocale.isDefined()) {
LOG.debug("Requested locale: {}", requestedLocale.getValue());
}
return requestedLocale;
}
/**
* Save the given locale to the ActionInvocation.
*
* @param invocation The ActionInvocation.
* @param locale The locale to save.
*/
protected void useLocale(ActionInvocation invocation, Locale locale) {
invocation.getInvocationContext().setLocale(locale);
}
/**
* Uses to handle reading/storing Locale from/in different locations
*/
protected interface LocaleHandler {
Locale find();
Locale read(ActionInvocation invocation);
Locale store(ActionInvocation invocation, Locale locale);
boolean shouldStore();
}
protected class RequestOnlyLocaleHandler implements LocaleHandler {
protected ActionInvocation actionInvocation = null;
protected boolean shouldStore = true;
protected RequestOnlyLocaleHandler(ActionInvocation invocation) {
actionInvocation = invocation;
}
public Locale find() {
LOG.debug("Searching locale in request under parameter {}", requestOnlyParameterName);
Parameter requestedLocale = findLocaleParameter(actionInvocation, requestOnlyParameterName);
if (requestedLocale.isDefined()) {
return getLocaleFromParam(requestedLocale.getValue());
}
return null;
}
@Override
public Locale store(ActionInvocation invocation, Locale locale) {
return locale;
}
@Override
public Locale read(ActionInvocation invocation) {
LOG.debug("Searching current Invocation context");
// no overriding locale definition found, stay with current invocation (=browser) locale
Locale locale = invocation.getInvocationContext().getLocale();
if (locale != null) {
LOG.debug("Applied invocation context locale: {}", locale);
}
return locale;
}
@Override
public boolean shouldStore() {
return shouldStore;
}
}
protected class SessionLocaleHandler extends RequestOnlyLocaleHandler {
protected SessionLocaleHandler(ActionInvocation invocation) {
super(invocation);
}
@Override
public Locale find() {
Locale requestOnlyLocale = super.find();
if (requestOnlyLocale != null) {
LOG.debug("Found locale under request only param, it won't be stored in session!");
shouldStore = false;
return requestOnlyLocale;
}
LOG.debug("Searching locale in request under parameter {}", parameterName);
Parameter requestedLocale = findLocaleParameter(actionInvocation, parameterName);
if (requestedLocale.isDefined()) {
return getLocaleFromParam(requestedLocale.getValue());
}
return null;
}
@Override
public Locale store(ActionInvocation invocation, Locale locale) {
Map<String, Object> session = invocation.getInvocationContext().getSession();
if (session != null) {
String sessionId = ServletActionContext.getRequest().getSession().getId();
synchronized (sessionId.intern()) {
session.put(attributeName, locale);
}
}
return locale;
}
@Override
public Locale read(ActionInvocation invocation) {
Locale locale = null;
LOG.debug("Checks session for saved locale");
HttpSession session = ServletActionContext.getRequest().getSession(false);
if (session != null) {
String sessionId = session.getId();
synchronized (sessionId.intern()) {
Object sessionLocale = invocation.getInvocationContext().getSession().get(attributeName);
if (sessionLocale != null && sessionLocale instanceof Locale) {
locale = (Locale) sessionLocale;
LOG.debug("Applied session locale: {}", locale);
}
}
}
if (locale == null) {
LOG.debug("No Locale defined in session, fetching from current request and it won't be stored in session!");
shouldStore = false;
locale = super.read(invocation);
} else {
LOG.debug("Found stored Locale {} in session, using it!", locale);
}
return locale;
}
}
protected class CookieLocaleHandler extends RequestOnlyLocaleHandler {
protected CookieLocaleHandler(ActionInvocation invocation) {
super(invocation);
}
@Override
public Locale find() {
Locale requestOnlySessionLocale = super.find();
if (requestOnlySessionLocale != null) {
shouldStore = false;
return requestOnlySessionLocale;
}
LOG.debug("Searching locale in request under parameter {}", requestCookieParameterName);
Parameter requestedLocale = findLocaleParameter(actionInvocation, requestCookieParameterName);
if (requestedLocale.isDefined()) {
return getLocaleFromParam(requestedLocale.getValue());
}
return null;
}
@Override
public Locale store(ActionInvocation invocation, Locale locale) {
HttpServletResponse response = ServletActionContext.getResponse();
Cookie cookie = new Cookie(attributeName, locale.toString());
cookie.setMaxAge(1209600); // two weeks
response.addCookie(cookie);
return locale;
}
@Override
public Locale read(ActionInvocation invocation) {
Locale locale = null;
Cookie[] cookies = ServletActionContext.getRequest().getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (attributeName.equals(cookie.getName())) {
locale = getLocaleFromParam(cookie.getValue());
}
}
}
if (locale == null) {
LOG.debug("No Locale defined in cookie, fetching from current request and it won't be stored!");
shouldStore = false;
locale = super.read(invocation);
} else {
LOG.debug("Found stored Locale {} in cookie, using it!", locale);
}
return locale;
}
}
}