/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.i18n; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apereo.portal.properties.PropertiesManager; import org.apereo.portal.security.IPerson; import org.apereo.portal.utils.DocumentFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Manages locales on behalf of a user. This class currently keeps track of locales at the following * levels:<br> * * <ol> * <li>User's locale preferences (associated with a user ID) * <li>Browser's locale preferences (from the Accept-Language request header) * <li>Session's locale preferences (set via the portal request parameter uP_locales) * <li>Portal's locale preferences (set in portal.properties) * </ol> * * Eventually, this class will also keep track of locale preferences at the following levels:<br> * * <ol> * <li>Layout node's locale preferences * <li>User profile's locale preferences * </ol> * */ public class LocaleManager implements Serializable { private static final Log log = LogFactory.getLog(LocaleManager.class); /** * Default value for localeAware. This value will be used when the corresponding property cannot * be loaded. */ public static final boolean DEFAULT_LOCALE_AWARE = false; private static boolean localeAware = PropertiesManager.getPropertyAsBoolean( "org.apereo.portal.i18n.LocaleManager.locale_aware", DEFAULT_LOCALE_AWARE); private static Locale jvmLocale; private static Locale[] portalLocales; private final IPerson person; private Locale[] sessionLocales; private Locale[] browserLocales; private Locale[] userLocales; /** * Constructor that associates a locale manager with a user. * * @param person the user */ public LocaleManager(IPerson person, Locale[] userLocales) { this.person = person; jvmLocale = Locale.getDefault(); if (localeAware) { portalLocales = loadPortalLocales(); try { this.userLocales = userLocales; } catch (Exception e) { log.error("Error populating userLocals", e); } } } /** * Constructor that sets up locales according to the <code>Accept-Language</code> request header * from a user's browser. * * @param person the user * @param acceptLanguage the Accept-Language request header from a user's browser */ public LocaleManager(IPerson person, Locale[] userLocales, String acceptLanguage) { this(person, userLocales); this.browserLocales = parseLocales(acceptLanguage); } // Getters public static boolean isLocaleAware() { return localeAware; } public static Locale getJvmLocale() { return jvmLocale; } public static Locale[] getPortalLocales() { return portalLocales; } public Locale[] getBrowserLocales() { return browserLocales; } public Locale[] getUserLocales() { return userLocales; } public Locale[] getSessionLocales() { return sessionLocales; } // Setters public static void setJvmLocale(Locale jvmLocale) { LocaleManager.jvmLocale = jvmLocale; } public static void setPortalLocales(Locale[] portalLocales) { LocaleManager.portalLocales = portalLocales; } public void setBrowserLocales(Locale[] browserLocales) { this.browserLocales = browserLocales; } public void setUserLocales(Locale[] userLocales) { this.userLocales = userLocales; this.sessionLocales = userLocales; } public void setSessionLocales(Locale[] sessionLocales) { this.sessionLocales = sessionLocales; } /** * Read and parse portal_locales from portal.properties. portal_locales will be in the form of a * comma-separated list, e.g. en_US,ja_JP,sv_SE */ private Locale[] loadPortalLocales() { String portalLocalesString = PropertiesManager.getProperty( "org.apereo.portal.i18n.LocaleManager.portal_locales"); return parseLocales(portalLocalesString); } /** * Produces a sorted list of locales according to locale preferences obtained from several * places. The following priority is given: session, user, browser, portal, and jvm. * * @return the sorted list of locales */ public Locale[] getLocales() { // Need logic to construct ordered locale list. // Consider creating a separate ILocaleResolver // interface to do this work. List locales = new ArrayList(); // Add highest priority locales first addToLocaleList(locales, sessionLocales); addToLocaleList(locales, userLocales); // We will ignore browser locales until we know how to // translate them into proper java.util.Locales //addToLocaleList(locales, browserLocales); addToLocaleList(locales, portalLocales); addToLocaleList(locales, new Locale[] {jvmLocale}); return (Locale[]) locales.toArray(new Locale[0]); } /** Add locales to the locale list if they aren't in there already */ private void addToLocaleList(List localeList, Locale[] locales) { if (locales != null) { for (int i = 0; i < locales.length; i++) { if (locales[i] != null && !localeList.contains(locales[i])) localeList.add(locales[i]); } } } /** * Helper method to produce a <code>java.util.Locale</code> array from a comma-delimited locale * string list, e.g. "en_US,ja_JP" * * @param localeStringList the locales to parse * @return an array of locales representing the locale string list */ public static Locale[] parseLocales(String localeStringList) { Locale[] locales = null; if (localeStringList != null) { StringTokenizer st = new StringTokenizer(localeStringList, ","); locales = new Locale[st.countTokens()]; for (int i = 0; st.hasMoreTokens(); i++) { String localeString = st.nextToken().trim(); locales[i] = parseLocale(localeString); } } return locales; } /** * Helper method to produce a <code>java.util.Locale</code> object from a locale string such as * en_US or ja_JP. * * @param localeString a locale string such as en_US * @return a java.util.Locale object representing the locale string */ public static Locale parseLocale(String localeString) { String language = null; String country = null; String variant = null; // Sometimes people specify "en-US" instead of "en_US", so // we'll try to clean that up. localeString = localeString.replaceAll("-", "_"); StringTokenizer st = new StringTokenizer(localeString, "_"); if (st.hasMoreTokens()) { language = st.nextToken(); } if (st.hasMoreTokens()) { country = st.nextToken(); } if (st.hasMoreTokens()) { variant = st.nextToken(); } Locale locale = null; if (variant != null) { locale = new Locale(language, country, variant); } else if (country != null) { locale = new Locale(language, country); } else if (language != null) { // Uncomment the following line // when we can count on JDK 1.4! //locale = new Locale(language); locale = new Locale(language, ""); } return locale; } /** * Constructs a comma-delimited list of locales that could be parsed back into a Locale array * with parseLocales(String localeStringList). * * @param locales the list of locales * @return a string representing the list of locales */ public static String stringValueOf(Locale[] locales) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < locales.length; i++) { Locale locale = locales[i]; sb.append(locale.toString()); if (i < locales.length - 1) { sb.append(","); } } return sb.toString(); } /** * Stores the user locales persistantly. * * @param userLocales the user locales preference * @throws Exception */ public void persistUserLocales(Locale[] userLocales) throws Exception { setUserLocales(userLocales); } /** * Creates an XML representation of a list of locales. * * @param locales the locale list * @return the locale list as XML */ public static Document xmlValueOf(Locale[] locales) { return xmlValueOf(locales, null); } /** * Creates an XML representation of a list of locales. If a selected locale is supplied, the XML * element representing the selected locale will have an attribute of selected with value of * true. This is helpful when constructing user interfaces that indicate which locale is * selected. * * @param locales the locale list * @param selectedLocale a locale that should be selected if it is in the list * @return the locale list as XML */ public static Document xmlValueOf(Locale[] locales, Locale selectedLocale) { Document doc = DocumentFactory.getThreadDocument(); // <locales> Element localesE = doc.createElement("locales"); for (int i = 0; i < locales.length; i++) { Element locE = doc.createElement("locale"); locE.setAttribute("displayName", locales[i].getDisplayName(locales[0])); locE.setAttribute("code", locales[i].toString()); // Mark which locale is the user's preference if (selectedLocale != null && selectedLocale.equals(locales[i])) { locE.setAttribute("selected", "true"); } // <language iso2="..." iso3="..." displayName="..."/> Element languageE = doc.createElement("language"); languageE.setAttribute("iso2", locales[i].getLanguage()); try { languageE.setAttribute("iso3", locales[i].getISO3Language()); } catch (Exception e) { // Do nothing } languageE.setAttribute("displayName", locales[i].getDisplayLanguage(locales[0])); locE.appendChild(languageE); // <country iso2="..." iso3="..." displayName="..."/> Element countryE = doc.createElement("country"); countryE.setAttribute("iso2", locales[i].getCountry()); try { countryE.setAttribute("iso3", locales[i].getISO3Country()); } catch (Exception e) { // Do nothing } countryE.setAttribute("displayName", locales[i].getDisplayCountry(locales[0])); locE.appendChild(countryE); // <variant code="..." displayName="..."/> Element variantE = doc.createElement("variant"); variantE.setAttribute("code", locales[i].getVariant()); variantE.setAttribute("displayName", locales[i].getDisplayVariant(locales[0])); locE.appendChild(variantE); localesE.appendChild(locE); } doc.appendChild(localesE); return doc; } public String toString() { StringBuffer sb = new StringBuffer(1024); sb.append("LocaleManager's locales").append("\n"); sb.append("-----------------------").append("\n"); sb.append("Session locales: "); if (sessionLocales != null) { sb.append(stringValueOf(sessionLocales)); } sb.append("\n"); sb.append("User locales: "); if (userLocales != null) { sb.append(stringValueOf(userLocales)); } sb.append("\n"); sb.append("Browser locales: "); if (browserLocales != null) { sb.append(stringValueOf(browserLocales)); } sb.append("\n"); sb.append("Portal locales: "); if (portalLocales != null) { sb.append(stringValueOf(portalLocales)); } sb.append("\n"); sb.append("JVM locale: "); if (jvmLocale != null) { sb.append(jvmLocale.toString()); } sb.append("\n"); sb.append("Sorted locales: "); Locale[] sortedLocales = getLocales(); if (sortedLocales != null) { sb.append(stringValueOf(sortedLocales)); } sb.append("\n"); return sb.toString(); } }