All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.i18n.CmsLocaleManager Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

The newest version!
/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library 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 library 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.
 *
 * For further information about Alkacon Software GmbH & Co. KG, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.i18n;

import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsUser;
import org.opencms.main.CmsEvent;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.I_CmsEventListener;
import org.opencms.main.OpenCms;
import org.opencms.monitor.CmsMemoryMonitor;
import org.opencms.util.CmsStringUtil;
import org.opencms.xml.I_CmsXmlDocument;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;

import com.cybozu.labs.langdetect.DetectorFactory;

/**
 * Manages the locales configured for this OpenCms installation.

* * Locale configuration is done in the configuration file opencms-system.xml * in the opencms/system/internationalization node and it's sub-nodes.

* * @since 6.0.0 */ public class CmsLocaleManager implements I_CmsEventListener { /** Runtime property name for locale handler. */ public static final String LOCALE_HANDLER = "class_locale_handler"; /** Locale to use for storing locale-independent XML contents. */ public static final Locale MASTER_LOCALE = Locale.ENGLISH; /** Request parameter to force encoding selection. */ public static final String PARAMETER_ENCODING = "__encoding"; /** Request parameter to force locale selection. */ public static final String PARAMETER_LOCALE = "__locale"; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsLocaleManager.class); /** The default locale, this is the first configured locale. */ private static Locale m_defaultLocale = Locale.ENGLISH; /** * Required for setting the default locale on the first possible time.

*/ static { setDefaultLocale(); } /** The set of available locale names. */ private List m_availableLocales; /** The default locale names (must be a subset of the available locale names). */ private List m_defaultLocales; /** Indicates if the locale manager is fully initialized. */ private boolean m_initialized; /** The configured locale handler. */ private I_CmsLocaleHandler m_localeHandler; /** The string value of the 'reuse-elements' option. */ private String m_reuseElementsStr; /** The OpenCms default time zone. */ private TimeZone m_timeZone; /** * Initializes a new CmsLocaleManager, called from the configuration.

*/ public CmsLocaleManager() { setDefaultLocale(); setTimeZone("GMT"); m_availableLocales = new ArrayList(); m_defaultLocales = new ArrayList(); m_localeHandler = new CmsDefaultLocaleHandler(); if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_I18N_CONFIG_START_0)); } // register this object as event listener OpenCms.addCmsEventListener(this, new int[] {I_CmsEventListener.EVENT_CLEAR_CACHES}); } /** * Initializes a new CmsLocaleManager, used for OpenCms runlevel 1 (unit tests) only.

* * @param defaultLocale the default locale to use */ public CmsLocaleManager(Locale defaultLocale) { setDefaultLocale(); setTimeZone("GMT"); m_initialized = false; m_availableLocales = new ArrayList(); m_defaultLocales = new ArrayList(); m_localeHandler = new CmsDefaultLocaleHandler(); m_defaultLocale = defaultLocale; m_defaultLocales.add(defaultLocale); m_availableLocales.add(defaultLocale); } /** * Returns the default locale configured in opencms-system.xml, * that is the first locale from the list provided * in the opencms/system/internationalization/localesdefault node.

* * @return the default locale configured in opencms-system.xml */ public static Locale getDefaultLocale() { return m_defaultLocale; } /** * Returns a locale created from the given full name.

* * The full name must consist of language code, * country code(optional), variant(optional) separated by "_".

* * This method will always return a valid Locale! * If the provided locale name is not valid (i.e. leads to an Exception * when trying to create the Locale, then the configured default Locale is returned.

* * @param localeName the full locale name * @return the locale or null if not available */ public static Locale getLocale(String localeName) { if (CmsStringUtil.isEmpty(localeName)) { return getDefaultLocale(); } Locale locale = null; if (OpenCms.getMemoryMonitor() != null) { // this may be used AFTER shutdown locale = OpenCms.getMemoryMonitor().getCachedLocale(localeName); } if (locale != null) { return locale; } try { if ("all".equals(localeName)) { locale = new Locale("all"); } else { locale = LocaleUtils.toLocale(localeName); } } catch (Throwable t) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_CREATE_LOCALE_FAILED_1, localeName), t); // map this error to the default locale locale = getDefaultLocale(); } if (OpenCms.getMemoryMonitor() != null) { // this may be used AFTER shutdown OpenCms.getMemoryMonitor().cacheLocale(localeName, locale); } return locale; } /** * Returns the locale names from the given List of locales as a comma separated String.

* * For example, if the input List contains {@link Locale#ENGLISH} and * {@link Locale#GERMANY}, the result will be "en, de_DE".

* * An empty String is returned if the input is null, or contains no elements.

* * @param locales the locales to generate a String from * * @return the locale names from the given List of locales as a comma separated String */ public static String getLocaleNames(List locales) { StringBuffer result = new StringBuffer(); if (locales != null) { Iterator i = locales.iterator(); while (i.hasNext()) { result.append(i.next().toString()); if (i.hasNext()) { result.append(", "); } } } return result.toString(); } /** * Returns a List of locales from an array of locale names.

* * @param localeNames array of locale names * @return a List of locales derived from the given locale names */ public static List getLocales(List localeNames) { List result = new ArrayList(localeNames.size()); for (int i = 0; i < localeNames.size(); i++) { result.add(getLocale(localeNames.get(i).toString().trim())); } return result; } /** * Returns a List of locales from a comma-separated string of locale names.

* * @param localeNames a comma-separated string of locale names * @return a List of locales derived from the given locale names */ public static List getLocales(String localeNames) { if (localeNames == null) { return null; } return getLocales(CmsStringUtil.splitAsList(localeNames, ',')); } /** *

* Extends a base name with locale suffixes and yields the list of extended names * in the order they typically should be used according to the given locale. *

*

* Example: If you have base name base and the locale with {@link String} representation de_DE, * the result will be (assuming en is the default locale): *

    *
  • for wantBase == false and defaultAsBase == false: [base_de_DE, base_de]
  • *
  • for wantBase == true and defaultAsBase == false: [base_de_DE, base_de, base]
  • *
  • for wantBase == false and defaultAsBase == true: [base_de_DE, base_de, base_en]
  • *
  • for wantBase == true and defaultAsBase == true: [base_de_DE, base_de, base, base_en]
  • *
* If the requested locale is a variant of the default locale, * the list will never contain the default locale as last element because it appears already earlier. * * @param basename the base name that should be extended by locale post-fixes * @param locale the locale for which the list of extensions should be generated. * @param wantBase flag, indicating if the base name without locale post-fix should be yielded as well. * @param defaultAsBase flag, indicating, if the variant with the default locale should be used as base. * @return the list of locale variants of the base name in the order they should be used. */ public static List getLocaleVariants( String basename, Locale locale, boolean wantBase, boolean defaultAsBase) { List result = new ArrayList(); if (null == basename) { return result; } else { String localeString = null == locale ? "" : "_" + locale.toString(); boolean wantDefaultAsBase = defaultAsBase && !(localeString.startsWith("_" + getDefaultLocale().toString())); while (!localeString.isEmpty()) { result.add(basename + localeString); localeString = localeString.substring(0, localeString.lastIndexOf('_')); } if (wantBase) { result.add(basename); } if (wantDefaultAsBase) { result.add(basename + "_" + getDefaultLocale().toString()); } return result; } } /** * Utility method to get the primary locale for a given resource.

* * @param cms the current CMS context * @param res the resource for which the locale should be retrieved * * @return the primary locale */ public static Locale getMainLocale(CmsObject cms, CmsResource res) { CmsLocaleManager localeManager = OpenCms.getLocaleManager(); List defaultLocales = null; // must switch project id in stored Admin context to match current project String defaultNames = null; try { defaultNames = cms.readPropertyObject(res, CmsPropertyDefinition.PROPERTY_LOCALE, true).getValue(); } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } if (defaultNames != null) { defaultLocales = localeManager.getAvailableLocales(defaultNames); } if ((defaultLocales == null) || (defaultLocales.isEmpty())) { // no default locales could be determined defaultLocales = localeManager.getDefaultLocales(); } Locale locale; // return the first default locale name if ((defaultLocales != null) && (defaultLocales.size() > 0)) { locale = defaultLocales.get(0); } else { locale = CmsLocaleManager.getDefaultLocale(); } return locale; } /** * Returns the content encoding set for the given resource.

* * The content encoding is controlled by the property {@link CmsPropertyDefinition#PROPERTY_CONTENT_ENCODING}, * which can be set on the resource or on a parent folder for all resources in this folder.

* * In case no encoding has been set, the default encoding from * {@link org.opencms.main.CmsSystemInfo#getDefaultEncoding()} is returned.

* * @param cms the current OpenCms user context * @param res the resource to read the encoding for * * @return the content encoding set for the given resource */ public static final String getResourceEncoding(CmsObject cms, CmsResource res) { String encoding = null; // get the encoding try { encoding = cms.readPropertyObject(res, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue(); if (encoding != null) { encoding = CmsEncoder.lookupEncoding(encoding.trim(), encoding); } } catch (CmsException e) { if (LOG.isInfoEnabled()) { LOG.info(Messages.get().getBundle().key(Messages.ERR_READ_ENCODING_PROP_1, res.getRootPath()), e); } } if (encoding == null) { encoding = OpenCms.getSystemInfo().getDefaultEncoding(); } return encoding; } /** * Sets the default locale of the Java VM to {@link Locale#ENGLISH} if the * current default has any other language then English set.

* * This is required because otherwise the default (English) resource bundles * would not be displayed for the English locale if a translated default locale exists.

* * Here's an example of how this issues shows up: * On a German server, the default locale usually is {@link Locale#GERMAN}. * All English translations for OpenCms are located in the "default" message files, for example * org.opencms.i18n.message.properties. If the German localization is installed, it will be * located in org.opencms.i18n.message_de.properties. If user has English selected * as his locale, the default Java lookup mechanism first tries to find * org.opencms.i18n.message_en.properties. However, this file does not exist, since the * English localization is kept in the default file. Next, the Java lookup mechanism tries to find the servers * default locale, which in this example is German. Since there is a German message file, the Java lookup mechanism * is finished and uses this German localization, not the default file. Therefore the * user get the German localization, not the English one. * Setting the default locale explicitly to English avoids this issue.

*/ private static void setDefaultLocale() { // set the default locale to english // this is required because otherwise the default (english) resource bundles // would not be displayed for the english locale if a translated locale exists Locale oldLocale = Locale.getDefault(); if (!(Locale.ENGLISH.getLanguage().equals(oldLocale.getLanguage()))) { // default language is not English try { Locale.setDefault(Locale.ENGLISH); if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info( Messages.get().getBundle().key(Messages.INIT_I18N_DEFAULT_LOCALE_2, Locale.ENGLISH, oldLocale)); } } catch (Exception e) { // any Exception: the locale has not been changed, so there may be issues with the English // localization but OpenCms will run in general CmsLog.INIT.error( Messages.get().getBundle().key( Messages.LOG_UNABLE_TO_SET_DEFAULT_LOCALE_2, Locale.ENGLISH, oldLocale), e); } } else { if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info( Messages.get().getBundle().key(Messages.INIT_I18N_KEEPING_DEFAULT_LOCALE_1, oldLocale)); } } // initialize the static member with the new default m_defaultLocale = Locale.getDefault(); } /** * Adds a locale to the list of available locales.

* * @param localeName the locale to add */ public void addAvailableLocale(String localeName) { Locale locale = getLocale(localeName); // add full variation (language / country / variant) if (!m_availableLocales.contains(locale)) { m_availableLocales.add(locale); if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_I18N_CONFIG_ADD_LOCALE_1, locale)); } } // add variation with only language and country locale = new Locale(locale.getLanguage(), locale.getCountry()); if (!m_availableLocales.contains(locale)) { m_availableLocales.add(locale); if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_I18N_CONFIG_ADD_LOCALE_1, locale)); } } // add variation with language only locale = new Locale(locale.getLanguage()); if (!m_availableLocales.contains(locale)) { m_availableLocales.add(locale); if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_I18N_CONFIG_ADD_LOCALE_1, locale)); } } } /** * Adds a locale to the list of default locales.

* * @param localeName the locale to add */ public void addDefaultLocale(String localeName) { Locale locale = getLocale(localeName); if (!m_defaultLocales.contains(locale)) { m_defaultLocales.add(locale); if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info( Messages.get().getBundle().key( Messages.INIT_I18N_CONFIG_DEFAULT_LOCALE_2, Integer.valueOf(m_defaultLocales.size()), locale)); } } } /** * Implements the CmsEvent interface, * the locale manager the events to clear * the list of cached keys .

* * @param event CmsEvent that has occurred */ public void cmsEvent(CmsEvent event) { switch (event.getType()) { case I_CmsEventListener.EVENT_CLEAR_CACHES: clearCaches(); break; default: // no operation } } /** * Returns the list of available {@link Locale}s configured in opencms-system.xml, * in the opencms/system/internationalization/localesconfigured node.

* * The list of configured available locales contains all locales that are allowed to be used in the VFS, * for example as languages in XML content files.

* * The available locales are a superset of the default locales, see {@link #getDefaultLocales()}.

* * It's possible to reduce the system default by setting the propery * {@link CmsPropertyDefinition#PROPERTY_AVAILABLE_LOCALES} * to a comma separated list of locale names. However, you can not add new available locales, * only remove from the configured list.

* * Note that if the localesconfigured node contains a locale variant for a specific country (e.g. de_DE), * then both that locale and the locale without the country suffix will be in the returned list. * * @return the list of available locale names, e.g. en, de * * @see #getDefaultLocales() */ public List getAvailableLocales() { return Collections.unmodifiableList(m_availableLocales); } /** * Returns an array of available locale names for the given resource.

* * @param cms the current cms permission object * @param resource the resource * * @return an array of available locale names * * @see #getAvailableLocales() */ public List getAvailableLocales(CmsObject cms, CmsResource resource) { String availableNames = null; try { availableNames = cms.readPropertyObject( resource, CmsPropertyDefinition.PROPERTY_AVAILABLE_LOCALES, true).getValue(); } catch (CmsException exc) { LOG.debug("Could not read available locales property for resource " + resource.getRootPath(), exc); } List result = null; if (availableNames != null) { result = getAvailableLocales(availableNames); } if ((result == null) || (result.size() == 0)) { return Collections.unmodifiableList(m_availableLocales); } else { return result; } } /** * Returns an array of available locale names for the given resource.

* * @param cms the current cms permission object * @param resourceName the name of the resource * * @return an array of available locale names * * @see #getAvailableLocales() */ public List getAvailableLocales(CmsObject cms, String resourceName) { String availableNames = null; try { availableNames = cms.readPropertyObject( resourceName, CmsPropertyDefinition.PROPERTY_AVAILABLE_LOCALES, true).getValue(); } catch (CmsException exc) { LOG.debug("Could not read available locales property for resource " + resourceName, exc); } List result = null; if (availableNames != null) { result = getAvailableLocales(availableNames); } if ((result == null) || (result.size() == 0)) { return Collections.unmodifiableList(m_availableLocales); } else { return result; } } /** * Returns a List of available locales from a comma separated string of locale names.

* * All names are filtered against the allowed available locales * configured in opencms-system.xml.

* * @param names a comma-separated String of locale names * @return List of locales created from the given locale names * * @see #getAvailableLocales() */ public List getAvailableLocales(String names) { return checkLocaleNames(getLocales(names)); } /** * Returns the best available locale present in the given XML content, or the default locale.

* * @param cms the current OpenCms user context * @param resource the resource * @param content the XML content * * @return the locale */ public Locale getBestAvailableLocaleForXmlContent(CmsObject cms, CmsResource resource, I_CmsXmlDocument content) { Locale locale = getDefaultLocale(cms, resource); if (!content.hasLocale(locale)) { // if the requested locale is not available, get the first matching default locale, // or the first matching available locale boolean foundLocale = false; if (content.getLocales().size() > 0) { List locales = getDefaultLocales(cms, resource); for (Locale defaultLocale : locales) { if (content.hasLocale(defaultLocale)) { locale = defaultLocale; foundLocale = true; break; } } if (!foundLocale) { locales = getAvailableLocales(cms, resource); for (Locale availableLocale : locales) { if (content.hasLocale(availableLocale)) { locale = availableLocale; foundLocale = true; break; } } } } } return locale; } /** * Tries to find the given requested locale (eventually simplified) in the collection of available locales, * if the requested locale is not found it will return the first match from the given list of default locales.

* * @param requestedLocale the requested locale, if this (or a simplified version of it) is available it will be returned * @param defaults a list of default locales to use in case the requested locale is not available * @param available the available locales to find a match in * * @return the best matching locale name or null if no name matches */ public Locale getBestMatchingLocale(Locale requestedLocale, List defaults, List available) { if ((available == null) || available.isEmpty()) { // no locales are available at all return null; } // the requested locale is the match we want to find most if (available.contains(requestedLocale)) { // check if the requested locale is directly available return requestedLocale; } if (requestedLocale.getVariant().length() > 0) { // locale has a variant like "en_EN_whatever", try only with language and country Locale check = new Locale(requestedLocale.getLanguage(), requestedLocale.getCountry(), ""); if (available.contains(check)) { return check; } } if (requestedLocale.getCountry().length() > 0) { // locale has a country like "en_EN", try only with language Locale check = new Locale(requestedLocale.getLanguage(), "", ""); if (available.contains(check)) { return check; } } // available locales do not match the requested locale if ((defaults == null) || defaults.isEmpty()) { // if we have no default locales we are out of luck return null; } // no match found for the requested locale, return the first match from the default locales return getFirstMatchingLocale(defaults, available); } /** * Returns the "the" default locale for the given resource.

* * It's possible to override the system default (see {@link #getDefaultLocale()}) by setting the property * {@link CmsPropertyDefinition#PROPERTY_LOCALE} to a comma separated list of locale names. * This property is inherited from the parent folders. * This method will return the first locale from that list.

* * The default locale must be contained in the set of configured available locales, * see {@link #getAvailableLocales()}. * In case an invalid locale has been set with the property, this locale is ignored and the * same result as {@link #getDefaultLocale()} is returned.

* * In case the property {@link CmsPropertyDefinition#PROPERTY_LOCALE} has not been set * on the resource or a parent folder, * this method returns the same result as {@link #getDefaultLocale()}.

* * @param cms the current cms permission object * @param resource the resource * @return an array of default locale names * * @see #getDefaultLocales() * @see #getDefaultLocales(CmsObject, String) */ public Locale getDefaultLocale(CmsObject cms, CmsResource resource) { List defaultLocales = getDefaultLocales(cms, resource); Locale result; if (defaultLocales.size() > 0) { result = defaultLocales.get(0); } else { result = getDefaultLocale(); } return result; } /** * Returns the "the" default locale for the given resource.

* * It's possible to override the system default (see {@link #getDefaultLocale()}) by setting the property * {@link CmsPropertyDefinition#PROPERTY_LOCALE} to a comma separated list of locale names. * This property is inherited from the parent folders. * This method will return the first locale from that list.

* * The default locale must be contained in the set of configured available locales, * see {@link #getAvailableLocales()}. * In case an invalid locale has been set with the property, this locale is ignored and the * same result as {@link #getDefaultLocale()} is returned.

* * In case the property {@link CmsPropertyDefinition#PROPERTY_LOCALE} has not been set * on the resource or a parent folder, * this method returns the same result as {@link #getDefaultLocale()}.

* * @param cms the current cms permission object * @param resourceName the name of the resource * @return an array of default locale names * * @see #getDefaultLocales() * @see #getDefaultLocales(CmsObject, String) */ public Locale getDefaultLocale(CmsObject cms, String resourceName) { List defaultLocales = getDefaultLocales(cms, resourceName); Locale result; if (defaultLocales.size() > 0) { result = defaultLocales.get(0); } else { result = getDefaultLocale(); } return result; } /** * Returns the list of default {@link Locale}s configured in opencms-system.xml, * in the opencms/system/internationalization/localesdefault node.

* * Since the default locale is always available, the result list will always contain at least one Locale.

* * It's possible to override the system default by setting the property * {@link CmsPropertyDefinition#PROPERTY_LOCALE} to a comma separated list of locale names. * This property is inherited from the parent folders.

* * The default locales must be a subset of the configured available locales, see {@link #getAvailableLocales()}. * In case an invalid locale has been set with the property, this locale is ignored.

* * The default locale names are used as a fallback mechanism in case a locale is requested * that can not be found, for example when delivering content form an XML content.

* * There is a list of default locales (instead of just one default locale) since there * are scenarios when one default is not enough. Consider the following example: * The main default locale is set to "en". An example XML content file contains just one language, * in this case "de" and not "en". Now a request is made to the file for the locale "fr". If * there would be only one default locale ("en"), we would have to give up. But since we allow more then * one default, we can deliver the "de" content instead of a blank page.

* * @return the list of default locale names, e.g. en, de * * @see #getAvailableLocales() */ public List getDefaultLocales() { return m_defaultLocales; } /** * Returns an array of default locales for the given resource.

* * Since the default locale is always available, the result list will always contain at least one Locale.

* * It's possible to override the system default (see {@link #getDefaultLocales()}) by setting the property * {@link CmsPropertyDefinition#PROPERTY_LOCALE} to a comma separated list of locale names. * This property is inherited from the parent folders.

* * The default locales must be a subset of the configured available locales, see {@link #getAvailableLocales()}. * In case an invalid locale has been set with the property, this locale is ignored.

* * In case the property {@link CmsPropertyDefinition#PROPERTY_LOCALE} has not been set * on the resource or a parent folder, * this method returns the same result as {@link #getDefaultLocales()}.

* * Use this method in case you need to get all configured default options for a resource, * if you just need the "the" default locale for a resource, * use {@link #getDefaultLocale(CmsObject, String)}.

* * @param cms the current cms permission object * @param resource the resource to read the default locale properties for * @return an array of default locale names * * @see #getDefaultLocales() * @see #getDefaultLocale(CmsObject, String) * @see #getDefaultLocales(CmsObject, String) * * @since 7.0.2 */ public List getDefaultLocales(CmsObject cms, CmsResource resource) { String defaultNames = null; try { defaultNames = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_LOCALE, true).getValue(); } catch (CmsException e) { LOG.warn(Messages.get().getBundle().key(Messages.ERR_READ_ENCODING_PROP_1, cms.getSitePath(resource)), e); } return getDefaultLocales(defaultNames); } /** * Returns an array of default locales for the given resource.

* * Since the default locale is always available, the result list will always contain at least one Locale.

* * It's possible to override the system default (see {@link #getDefaultLocales()}) by setting the property * {@link CmsPropertyDefinition#PROPERTY_LOCALE} to a comma separated list of locale names. * This property is inherited from the parent folders.

* * The default locales must be a subset of the configured available locales, see {@link #getAvailableLocales()}. * In case an invalid locale has been set with the property, this locale is ignored.

* * In case the property {@link CmsPropertyDefinition#PROPERTY_LOCALE} has not been set * on the resource or a parent folder, * this method returns the same result as {@link #getDefaultLocales()}.

* * Use this method in case you need to get all configured default options for a resource, * if you just need the "the" default locale for a resource, * use {@link #getDefaultLocale(CmsObject, String)}.

* * @param cms the current cms permission object * @param resourceName the name of the resource * @return an array of default locale names * * @see #getDefaultLocales() * @see #getDefaultLocale(CmsObject, String) * @see #getDefaultLocales(CmsObject, CmsResource) */ public List getDefaultLocales(CmsObject cms, String resourceName) { String defaultNames = null; try { defaultNames = cms.readPropertyObject(resourceName, CmsPropertyDefinition.PROPERTY_LOCALE, true).getValue(); } catch (CmsException e) { LOG.warn(Messages.get().getBundle().key(Messages.ERR_READ_ENCODING_PROP_1, resourceName), e); } return getDefaultLocales(defaultNames); } /** * Returns the first matching locale (eventually simplified) from the available locales.

* * In case no match is found, code null is returned.

* * @param locales must be an ascending sorted list of locales in order of preference * @param available the available locales to find a match in * * @return the first precise or simplified match, or null in case no match is found */ public Locale getFirstMatchingLocale(List locales, List available) { Iterator i; // first try a precise match i = locales.iterator(); while (i.hasNext()) { Locale locale = i.next(); if (available.contains(locale)) { // precise match return locale; } } // now try a match only with language and country i = locales.iterator(); while (i.hasNext()) { Locale locale = i.next(); if (locale.getVariant().length() > 0) { // the locale has a variant, try to match without the variant locale = new Locale(locale.getLanguage(), locale.getCountry(), ""); if (available.contains(locale)) { // match return locale; } } } // finally try a match only with language i = locales.iterator(); while (i.hasNext()) { Locale locale = i.next(); if (locale.getCountry().length() > 0) { // the locale has a country, try to match without the country locale = new Locale(locale.getLanguage(), "", ""); if (available.contains(locale)) { // match return locale; } } } // no match return null; } /** * Returns the the appropriate locale/encoding for a request, * using the "right" locale handler for the given resource.

* * Certain system folders (like the Workplace) require a special * locale handler different from the configured handler. * Use this method if you want to resolve locales exactly like * the system does for a request.

* * @param req the current http request * @param user the current user * @param project the current project * @param resource the URI of the requested resource (with full site root added) * * @return the i18n information to use for the given request context */ public CmsI18nInfo getI18nInfo(HttpServletRequest req, CmsUser user, CmsProject project, String resource) { CmsI18nInfo i18nInfo = null; // check if this is a request against a Workplace folder if (OpenCms.getSiteManager().isWorkplaceRequest(req)) { // The list of configured localized workplace folders List wpLocalizedFolders = OpenCms.getWorkplaceManager().getLocalizedFolders(); for (int i = wpLocalizedFolders.size() - 1; i >= 0; i--) { if (resource.startsWith(wpLocalizedFolders.get(i))) { // use the workplace locale handler for this resource i18nInfo = OpenCms.getWorkplaceManager().getI18nInfo(req, user, project, resource); break; } } } if (i18nInfo == null) { // use default locale handler i18nInfo = m_localeHandler.getI18nInfo(req, user, project, resource); } // check the request for special parameters overriding the locale handler Locale locale = null; String encoding = null; if (req != null) { String localeParam = req.getParameter(CmsLocaleManager.PARAMETER_LOCALE); // check request for parameters if (localeParam != null) { // "__locale" parameter found in request locale = CmsLocaleManager.getLocale(localeParam); } // check for "__encoding" parameter in request encoding = req.getParameter(CmsLocaleManager.PARAMETER_ENCODING); } // merge values from request with values from locale handler if (locale == null) { locale = i18nInfo.getLocale(); } if (encoding == null) { encoding = i18nInfo.getEncoding(); } // still some values might be "null" if (locale == null) { locale = getDefaultLocale(); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_LOCALE_NOT_FOUND_1, locale)); } } if (encoding == null) { encoding = OpenCms.getSystemInfo().getDefaultEncoding(); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_ENCODING_NOT_FOUND_1, encoding)); } } // return the merged values return new CmsI18nInfo(locale, encoding); } /** * Returns the configured locale handler.

* * This handler is used to derive the appropriate locale/encoding for a request.

* * @return the locale handler */ public I_CmsLocaleHandler getLocaleHandler() { return m_localeHandler; } /** * Gets the string value of the 'reuse-elements' option.

* * @return the string value of the 'reuse-elements' option */ public String getReuseElementsStr() { return m_reuseElementsStr; } /** * Returns the OpenCms default the time zone.

* * @return the OpenCms default the time zone */ public TimeZone getTimeZone() { return m_timeZone; } /** * Initializes this locale manager with the OpenCms system configuration.

* * @param cms an OpenCms context object that must have been initialized with "Admin" permissions */ public void initialize(CmsObject cms) { if (!m_availableLocales.contains(Locale.ENGLISH)) { throw new RuntimeException("The locale 'en' must be configured in opencms-system.xml."); } // init the locale handler m_localeHandler.initHandler(cms); // set default locale m_defaultLocale = m_defaultLocales.get(0); initLanguageDetection(); // set initialized status m_initialized = true; if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_I18N_CONFIG_VFSACCESS_0)); } } /** * Returns true if this locale manager is fully initialized.

* * This is required to prevent errors during unit tests, * simple unit tests will usually not have a fully * initialized locale manager available.

* * @return true if the locale manager is fully initialized */ public boolean isInitialized() { return m_initialized; } /** * Sets the configured locale handler.

* * @param localeHandler the locale handler to set */ public void setLocaleHandler(I_CmsLocaleHandler localeHandler) { if (localeHandler != null) { m_localeHandler = localeHandler; } if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info( Messages.get().getBundle().key( Messages.INIT_I18N_CONFIG_LOC_HANDLER_1, m_localeHandler.getClass().getName())); } } /** * Sets the 'reuse-elemnts option value.

* * @param reuseElements the option value */ public void setReuseElements(String reuseElements) { m_reuseElementsStr = reuseElements; } /** * Sets OpenCms default the time zone.

* * If the name can not be resolved as time zone ID, then "GMT" is used.

* * @param timeZoneName the name of the time zone to set, for example "GMT" */ public void setTimeZone(String timeZoneName) { // according to JavaDoc, "GMT" is the default time zone if the name can not be resolved m_timeZone = TimeZone.getTimeZone(timeZoneName); } /** * Returns true if the 'copy page' dialog should reuse elements in auto mode when copying to a different locale.

* * @return true if auto mode of the 'copy page' dialog should reuse elements */ public boolean shouldReuseElements() { boolean isFalseInConfig = Boolean.FALSE.toString().equalsIgnoreCase(StringUtils.trim(m_reuseElementsStr)); return !isFalseInConfig; } /** * Returns a list of available locale names derived from the given locale names.

* * Each name in the given list is checked against the internal hash map of allowed locales, * and is appended to the resulting list only if the locale exists.

* * @param locales List of locales to check * @return list of available locales derived from the given locale names */ private List checkLocaleNames(List locales) { if (locales == null) { return null; } List result = new ArrayList(); Iterator i = locales.iterator(); while (i.hasNext()) { Locale locale = i.next(); if (m_availableLocales.contains(locale)) { result.add(locale); } } return result; } /** * Clears the caches in the locale manager.

*/ private void clearCaches() { // flush all caches OpenCms.getMemoryMonitor().flushCache(CmsMemoryMonitor.CacheType.LOCALE); CmsResourceBundleLoader.flushBundleCache(); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_LOCALE_MANAGER_FLUSH_CACHE_1, "EVENT_CLEAR_CACHES")); } } /** * Internal helper, returns an array of default locales for the given default names.

* * If required returns the system configured default locales.

* * @param defaultNames the default locales to use, can be null or a comma separated list * of locales, for example "en, de" * * @return an array of default locales for the given default names */ private List getDefaultLocales(String defaultNames) { List result = null; if (defaultNames != null) { result = getAvailableLocales(defaultNames); } if ((result == null) || (result.size() == 0)) { return getDefaultLocales(); } else { return result; } } /** * Initializes the language detection.

*/ private void initLanguageDetection() { try { // use a seed for initializing the language detection for making sure the // same probabilities are detected for the same document contents DetectorFactory.clear(); DetectorFactory.setSeed(42L); DetectorFactory.loadProfile(loadProfiles(getAvailableLocales())); } catch (Exception e) { LOG.error(Messages.get().getBundle().key(Messages.INIT_I18N_LANG_DETECT_FAILED_0), e); } } /** * Load the profiles from the classpath.

* * @param locales the locales to initialize.

* * @return a list of profiles * * @throws Exception if something goes wrong */ private List loadProfiles(List locales) throws Exception { List profiles = new ArrayList(); List languagesAdded = new ArrayList(); for (Locale locale : locales) { try { String lang = locale.getLanguage(); // make sure not to add a profile twice if (!languagesAdded.contains(lang)) { languagesAdded.add(lang); String profileFile = "profiles" + "/" + lang; InputStream is = getClass().getClassLoader().getResourceAsStream(profileFile); if (is != null) { String profile = IOUtils.toString(is, "UTF-8"); if ((profile != null) && (profile.length() > 0)) { profiles.add(profile); } is.close(); } else { LOG.warn( Messages.get().getBundle().key( Messages.INIT_I18N_LAND_DETECT_PROFILE_NOT_AVAILABLE_1, locale)); } } } catch (Exception e) { LOG.error( Messages.get().getBundle().key(Messages.INIT_I18N_LAND_DETECT_LOADING_PROFILE_FAILED_1, locale), e); } } return profiles; } }