/* * $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.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); } 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) { //save it in session 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"); Map<String, Object> session = invocation.getInvocationContext().getSession(); if (session != null) { //[WW-4741] Do not force session creation while this is a read operation HttpSession httpSession = ServletActionContext.getRequest().getSession(false); if(null != httpSession) { String sessionId = httpSession.getId(); synchronized (sessionId.intern()) { Object sessionLocale = session.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; } } }