/* * 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.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Locale; import java.util.Set; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.component.ComponentRequestLifecycle; import org.exoplatform.container.component.RequestLifeCycle; import org.exoplatform.container.web.AbstractFilter; import org.exoplatform.portal.Constants; import org.exoplatform.portal.application.PortalRequestContext; 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; /** * This filter provides {@link HttpServletRequest#getLocale()} and {@link HttpServletRequest#getLocales()} override for * extra-portlet requests (i.e. unbridged .jsp). Thanks to it dynamic resources can be localized to keep in sync with the rest * of the portal. This filter is re-entrant, and can safely be installed for INCLUDE, FORWARD, and ERROR dispatch methods. * <p> * A concrete example of re-entrant use is login/jsp/login.jsp used when authentication fails at portal login. * <p> * By default {@link HttpServletRequest#getLocale()} and {@link HttpServletRequest#getLocales()} reflect browser language * preference. When using this filter these two calls employ the same Locale determination algorithm as * {@link LocalizationLifecycle} does. * <p> * This filter can be activated / deactivated via portal module's web.xml * <p> * If default portal language is other than English, it can be configured for the filter by using PortalLocale init param: * <p> * * <pre> * <filter> * <filter-name>LocalizationFilter</filter-name> * <filter-class>org.exoplatform.portal.application.localization.LocalizationFilter</filter-class> * <init-param> * <param-name>PortalLocale</param-name> * <param-value>fr_FR</param-value> * </init-param> * </filter> * </pre> * * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a> */ public class LocalizationFilter extends AbstractFilter { private static Log log = ExoLogger.getLogger("portal:LocalizationFilter"); private static ThreadLocal<Locale> currentLocale = new ThreadLocal<Locale>(); private Locale portalLocale = Locale.ENGLISH; @Override protected void afterInit(FilterConfig config) throws ServletException { String locale = config.getInitParameter("PortalLocale"); locale = locale != null ? locale.trim() : null; if (locale != null && locale.length() > 0) portalLocale = LocaleContextInfo.getLocale(locale); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; try { // Due to forwards, and includes the filter might be reentered // If current requestContext exists use its Locale PortalRequestContext context = PortalRequestContext.getCurrentInstance(); if (context != null && context.getLocale() != null) { // No need to wrap if reentered boolean skipWrapping = currentLocale.get() != null; // overwrite any already set currentLocale currentLocale.set(context.getLocale()); if (!skipWrapping) { req = new HttpRequestWrapper(req); } chain.doFilter(req, res); return; } // If reentered we don't need to wrap if (currentLocale.get() != null) { chain.doFilter(request, response); return; } // Initialize currentLocale ExoContainer container = getContainer(); if (container == null) { // Nothing we can do, move on chain.doFilter(req, res); return; } LocaleConfigService localeConfigService = (LocaleConfigService) container .getComponentInstanceOfType(LocaleConfigService.class); LocalePolicy localePolicy = (LocalePolicy) container.getComponentInstanceOfType(LocalePolicy.class); LocaleContextInfo localeCtx = new LocaleContextInfo(); Set<Locale> supportedLocales = new HashSet(); for (LocaleConfig lc : localeConfigService.getLocalConfigs()) { supportedLocales.add(lc.getLocale()); } localeCtx.setSupportedLocales(supportedLocales); localeCtx.setBrowserLocales(Collections.list(request.getLocales())); localeCtx.setCookieLocales(LocalizationLifecycle.getCookieLocales(req)); localeCtx.setSessionLocale(LocalizationLifecycle.getSessionLocale(req)); localeCtx.setUserProfileLocale(getUserProfileLocale(container, req.getRemoteUser())); localeCtx.setRemoteUser(req.getRemoteUser()); localeCtx.setPortalLocale(checkPortalLocaleSupported(portalLocale, supportedLocales)); 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; } currentLocale.set(locale); chain.doFilter(new HttpRequestWrapper(req), res); } catch (Exception e) { throw new RuntimeException("LocalizationFilter exception: ", e); } finally { currentLocale.remove(); } } private Locale checkPortalLocaleSupported(Locale portalLocale, Set<Locale> supportedLocales) { if (supportedLocales.contains(portalLocale)) return portalLocale; if ("".equals(portalLocale.getCountry()) == false) { Locale loc = new Locale(portalLocale.getLanguage()); if (supportedLocales.contains(loc)) { log.warn("portalLocale not supported: " + LocaleContextInfo.getLocaleAsString(portalLocale) + ". Falling back to '" + portalLocale.getLanguage() + "'."); this.portalLocale = loc; return loc; } } log.warn("portalLocale not supported: " + LocaleContextInfo.getLocaleAsString(portalLocale) + ". Falling back to Locale.ENGLISH."); this.portalLocale = Locale.ENGLISH; return portalLocale; } private Locale getUserProfileLocale(ExoContainer container, String user) { UserProfile userProfile = null; OrganizationService svc = (OrganizationService) container.getComponentInstanceOfType(OrganizationService.class); if (user != null) { try { beginContext(svc); userProfile = svc.getUserProfileHandler().findUserProfileByName(user); } catch (Exception ignored) { log.error("IGNORED: Failed to load UserProfile for username: " + user, ignored); } finally { try { endContext(svc); } catch (Exception ignored) { // we don't care } } if (userProfile == null && log.isDebugEnabled()) log.debug("Could not load user profile for " + user); } String lang = userProfile == null ? null : userProfile.getUserInfoMap().get(Constants.USER_LANGUAGE); return (lang != null) ? LocaleContextInfo.getLocale(lang) : null; } public void beginContext(OrganizationService orgService) { if (orgService instanceof ComponentRequestLifecycle) { RequestLifeCycle.begin((ComponentRequestLifecycle) orgService); } } public void endContext(OrganizationService orgService) { // do the same check as in beginContext to make it symmetric if (orgService instanceof ComponentRequestLifecycle) { RequestLifeCycle.end(); } } public void destroy() { } public static Locale getCurrentLocale() { return currentLocale.get(); } }