/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.portal.application.localization;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.portal.Constants;
import org.exoplatform.portal.application.PortalRequestContext;
import org.exoplatform.portal.application.UserProfileLifecycle;
import org.exoplatform.portal.config.DataStorage;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.webui.workspace.UIPortalApplication;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.UserProfile;
import org.exoplatform.services.resources.LocaleConfig;
import org.exoplatform.services.resources.LocaleConfigService;
import org.exoplatform.services.resources.LocaleContextInfo;
import org.exoplatform.services.resources.LocalePolicy;
import org.exoplatform.web.application.Application;
import org.exoplatform.web.application.ApplicationRequestPhaseLifecycle;
import org.exoplatform.web.application.Phase;
import org.exoplatform.web.application.RequestFailure;
import org.exoplatform.webui.application.WebuiRequestContext;
/**
* This class takes care of loading / initializing / saving the current Locale. Current Locale is used to create properly
* localized response to current request.
*
* At the beginning of request {@link LocalePolicy} is used to determine the initial Locale to be used for processing the
* request.
*
* This Locale is then set on current {@link org.exoplatform.portal.application.PortalRequestContext} (it's presumed that
* current {@link org.exoplatform.web.application.RequestContext} is of type PortalRequestContext) by calling
* {@link org.exoplatform.portal.application.PortalRequestContext#setLocale}.
*
* During request processing {@link org.exoplatform.portal.application.PortalRequestContext#getLocale} is the ultimate reference
* consulted by any rendering code that needs to know about current Locale.
*
* When this Locale is changed during action processing, the new Locale choice is saved into user's profile or into browser's
* cookie in order to be used by future requests.
*
* This Lifecycle depends on UserProfileLifecycle being registered before this one, as it relies on it for loading the user
* profile. See WEB-INF/webui-configuration.xml in web/portal module.
*
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class LocalizationLifecycle implements ApplicationRequestPhaseLifecycle<WebuiRequestContext> {
private static final String LOCALE_COOKIE = "LOCALE";
private static final String LOCALE_SESSION_ATTR = "org.gatein.LOCALE";
private static final String PREV_LOCALE_SESSION_ATTR = "org.gatein.LAST_LOCALE";
private static final ThreadLocal<Locale> calculatedLocale = new ThreadLocal<Locale>();
private static Log log = ExoLogger.getLogger("portal:LocalizationLifecycle");
/**
* @see org.exoplatform.web.application.ApplicationLifecycle#onInit
*/
public void onInit(Application app) throws Exception {
}
/**
* Initialize Locale to be used for the processing of current request
*
* @see org.exoplatform.web.application.ApplicationLifecycle#onStartRequest
*/
public void onStartRequest(Application app, WebuiRequestContext context) throws Exception {
if (context instanceof PortalRequestContext == false)
throw new IllegalArgumentException("Expected PortalRequestContext, but got: " + context);
PortalRequestContext reqCtx = (PortalRequestContext) context;
ExoContainer container = app.getApplicationServiceContainer();
LocaleConfigService localeConfigService = (LocaleConfigService) container
.getComponentInstanceOfType(LocaleConfigService.class);
LocalePolicy localePolicy = (LocalePolicy) container.getComponentInstanceOfType(LocalePolicy.class);
LocaleContextInfo localeCtx = new LocaleContextInfo();
Set<Locale> supportedLocales = new HashSet<Locale>();
for (LocaleConfig lc : localeConfigService.getLocalConfigs()) {
supportedLocales.add(lc.getLocale());
}
localeCtx.setSupportedLocales(supportedLocales);
HttpServletRequest request = HttpServletRequest.class.cast(context.getRequest());
localeCtx.setBrowserLocales(Collections.list(request.getLocales()));
localeCtx.setCookieLocales(getCookieLocales(request));
localeCtx.setSessionLocale(getSessionLocale(request));
localeCtx.setUserProfileLocale(getUserProfileLocale(reqCtx));
localeCtx.setRemoteUser(reqCtx.getRemoteUser());
DataStorage dataStorage = (DataStorage) container.getComponentInstanceOfType(DataStorage.class);
PortalConfig pConfig = null;
try {
pConfig = dataStorage.getPortalConfig(SiteType.PORTAL.getName(), reqCtx.getPortalOwner());
if (pConfig == null)
log.warn("No UserPortalConfig available! Portal locale set to 'en'");
} catch (Exception ignored) {
if (log.isDebugEnabled())
log.debug("IGNORED: Failed to load UserPortalConfig: ", ignored);
}
String portalLocaleName = "en";
if (pConfig != null)
portalLocaleName = pConfig.getLocale();
Locale portalLocale = LocaleContextInfo.getLocale(portalLocaleName);
localeCtx.setPortalLocale(portalLocale);
localeCtx.setRequestLocale(reqCtx.getRequestLocale());
Locale locale = localePolicy.determineLocale(localeCtx);
boolean supported = supportedLocales.contains(locale);
if (!supported && !"".equals(locale.getCountry())) {
locale = new Locale(locale.getLanguage());
supported = supportedLocales.contains(locale);
}
if (!supported) {
if (log.isWarnEnabled())
log.warn("Unsupported locale returned by LocalePolicy: " + localePolicy + ". Falling back to 'en'.");
locale = Locale.ENGLISH;
}
reqCtx.setLocale(locale);
calculatedLocale.set(locale);
resetOrientation(reqCtx, locale);
}
/**
* @see org.exoplatform.web.application.ApplicationRequestPhaseLifecycle#onStartRequestPhase
*/
public void onStartRequestPhase(Application app, WebuiRequestContext context, Phase phase) {
}
/**
* Save any locale change - to cookie for anonymous users, to profile for logged-in users
*
* @see org.exoplatform.web.application.ApplicationRequestPhaseLifecycle#onEndRequestPhase
*/
public void onEndRequestPhase(Application app, WebuiRequestContext context, Phase phase) {
if (phase == Phase.ACTION) {
// if onStartRequest survived the cast, this one should as well - no check necessary
PortalRequestContext reqCtx = (PortalRequestContext) context;
Locale loc = reqCtx.getLocale();
Locale remembered = calculatedLocale.get();
calculatedLocale.remove();
boolean refreshNeeded = false;
// if locale changed since previous request
Locale sessLocale = getPreviousLocale(reqCtx.getRequest());
if (loc != null && sessLocale != null && !loc.equals(sessLocale)) {
refreshNeeded = true;
}
// if locale changed during this request's processing
if (loc != null && (remembered == null || !loc.equals(remembered))) {
refreshNeeded = true;
saveLocale(reqCtx, loc);
}
if (refreshNeeded) {
resetOrientation(reqCtx, loc);
}
savePreviousLocale(reqCtx, loc);
}
}
/**
* @see org.exoplatform.web.application.ApplicationLifecycle#onEndRequest
*/
public void onEndRequest(Application app, WebuiRequestContext context) throws Exception {
}
/**
* @see org.exoplatform.web.application.ApplicationLifecycle#onFailRequest
*/
public void onFailRequest(Application app, WebuiRequestContext context, RequestFailure failureType) {
}
/**
* @see org.exoplatform.web.application.ApplicationLifecycle#onDestroy
*/
public void onDestroy(Application app) throws Exception {
}
/**
* Use {@link UserProfile} already loaded by {@link org.exoplatform.portal.application.UserProfileLifecycle} or load one
* ourselves.
*
* @param context current PortalRequestContext
* @return Locale from user's profile or null
*/
private Locale getUserProfileLocale(PortalRequestContext context) {
String lang = null;
UserProfile userProfile = getLoadedProfile(context);
lang = userProfile == null ? null : userProfile.getUserInfoMap().get(Constants.USER_LANGUAGE);
return (lang != null) ? LocaleContextInfo.getLocale(lang) : null;
}
private UserProfile loadUserProfile(ExoContainer container, PortalRequestContext context) {
UserProfile userProfile = null;
OrganizationService svc = (OrganizationService) container.getComponentInstanceOfType(OrganizationService.class);
String user = context.getRemoteUser();
if (user != null) {
try {
userProfile = svc.getUserProfileHandler().findUserProfileByName(user);
} catch (Exception ignored) {
log.error("IGNORED: Failed to load UserProfile for username: " + user, ignored);
}
if (userProfile == null && log.isWarnEnabled())
log.warn("Could not load user profile for " + user + ". Using default portal locale.");
}
return userProfile;
}
private UserProfile getLoadedProfile(PortalRequestContext context) {
return (UserProfile) context.getAttribute(UserProfileLifecycle.USER_PROFILE_ATTRIBUTE_NAME);
}
public static List<Locale> getCookieLocales(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (LOCALE_COOKIE.equals(cookie.getName())) {
List<Locale> locales = new ArrayList<Locale>();
locales.add(LocaleContextInfo.getLocale(cookie.getValue()));
return locales;
}
}
}
return Collections.emptyList();
}
public static Locale getSessionLocale(HttpServletRequest request) {
return getLocaleFromSession(request, LOCALE_SESSION_ATTR);
}
public static Locale getPreviousLocale(HttpServletRequest request) {
return getLocaleFromSession(request, PREV_LOCALE_SESSION_ATTR);
}
private static Locale getLocaleFromSession(HttpServletRequest request, String attrName) {
String lang = null;
HttpSession session = request.getSession(false);
if (session != null)
lang = (String) session.getAttribute(attrName);
return (lang != null) ? LocaleContextInfo.getLocale(lang) : null;
}
private void saveLocale(PortalRequestContext context, Locale loc) {
String user = context.getRemoteUser();
if (user != null) {
saveLocaleToUserProfile(context, loc, user);
} else {
saveLocaleToCookie(context, loc);
}
saveSessionLocale(context, loc);
}
private void resetOrientation(PortalRequestContext context, Locale loc) {
ExoContainer container = context.getApplication().getApplicationServiceContainer();
LocaleConfigService localeConfigService = (LocaleConfigService) container
.getComponentInstanceOfType(LocaleConfigService.class);
LocaleConfig localeConfig = localeConfigService.getLocaleConfig(LocaleContextInfo.getLocaleAsString(loc));
if (localeConfig == null) {
if (log.isWarnEnabled())
log.warn("Locale changed to unsupported Locale during request processing: " + loc);
return;
}
// we presume PortalRequestContext, and UIPortalApplication
((UIPortalApplication) context.getUIApplication()).setOrientation(localeConfig.getOrientation());
}
private void saveSessionLocale(PortalRequestContext context, Locale loc) {
saveLocaleToSession(context, LOCALE_SESSION_ATTR, loc);
}
private void savePreviousLocale(PortalRequestContext context, Locale loc) {
saveLocaleToSession(context, PREV_LOCALE_SESSION_ATTR, loc);
}
private void saveLocaleToSession(PortalRequestContext context, String attrName, Locale loc) {
HttpServletRequest res = context.getRequest();
HttpSession session = res.getSession(false);
if (session != null)
session.setAttribute(attrName, LocaleContextInfo.getLocaleAsString(loc));
}
private void saveLocaleToCookie(PortalRequestContext context, Locale loc) {
HttpServletResponse res = context.getResponse();
Cookie cookie = new Cookie(LOCALE_COOKIE, LocaleContextInfo.getLocaleAsString(loc));
cookie.setMaxAge(Integer.MAX_VALUE);
cookie.setPath("/");
res.addCookie(cookie);
}
private void saveLocaleToUserProfile(PortalRequestContext context, Locale loc, String user) {
ExoContainer container = context.getApplication().getApplicationServiceContainer();
OrganizationService svc = (OrganizationService) container.getComponentInstanceOfType(OrganizationService.class);
// Don't rely on UserProfileLifecycle loaded UserProfile when doing
// an update to avoid a potential overwrite of other changes
UserProfile userProfile = loadUserProfile(container, context);
if (userProfile != null) {
userProfile.getUserInfoMap().put(Constants.USER_LANGUAGE, LocaleContextInfo.getLocaleAsString(loc));
try {
svc.getUserProfileHandler().saveUserProfile(userProfile, false);
} catch (Exception ignored) {
log.error("IGNORED: Failed to save profile for user: " + user, ignored);
userProfile = null;
}
}
if (userProfile == null) {
if (log.isWarnEnabled())
log.warn("Unable to save locale into profile for user: " + user);
}
}
}