001: /*
002: * File : $Source: /usr/local/cvs/opencms/src/org/opencms/i18n/CmsLocaleManager.java,v $
003: * Date : $Date: 2008-02-27 12:05:47 $
004: * Version: $Revision: 1.56 $
005: *
006: * This library is part of OpenCms -
007: * the Open Source Content Management System
008: *
009: * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
010: *
011: * This library is free software; you can redistribute it and/or
012: * modify it under the terms of the GNU Lesser General Public
013: * License as published by the Free Software Foundation; either
014: * version 2.1 of the License, or (at your option) any later version.
015: *
016: * This library is distributed in the hope that it will be useful,
017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: * Lesser General Public License for more details.
020: *
021: * For further information about Alkacon Software GmbH, please see the
022: * company website: http://www.alkacon.com
023: *
024: * For further information about OpenCms, please see the
025: * project website: http://www.opencms.org
026: *
027: * You should have received a copy of the GNU Lesser General Public
028: * License along with this library; if not, write to the Free Software
029: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
030: */
031:
032: package org.opencms.i18n;
033:
034: import org.opencms.file.CmsObject;
035: import org.opencms.file.CmsProject;
036: import org.opencms.file.CmsPropertyDefinition;
037: import org.opencms.file.CmsResource;
038: import org.opencms.file.CmsUser;
039: import org.opencms.main.CmsEvent;
040: import org.opencms.main.CmsException;
041: import org.opencms.main.CmsLog;
042: import org.opencms.main.I_CmsEventListener;
043: import org.opencms.main.OpenCms;
044: import org.opencms.util.CmsStringUtil;
045:
046: import java.util.ArrayList;
047: import java.util.Collection;
048: import java.util.Iterator;
049: import java.util.List;
050: import java.util.Locale;
051:
052: import javax.servlet.http.HttpServletRequest;
053:
054: import org.apache.commons.logging.Log;
055:
056: /**
057: * Manages the locales configured for this OpenCms installation.<p>
058: *
059: * Locale configuration is done in the configuration file <code>opencms-system.xml</code>
060: * in the <code>opencms/system/internationalization</code> node and it's sub-nodes.<p>
061: *
062: * @author Carsten Weinholz
063: * @author Alexander Kandzior
064: *
065: * @version $Revision: 1.56 $
066: *
067: * @since 6.0.0
068: */
069: public class CmsLocaleManager implements I_CmsEventListener {
070:
071: /** Runtime property name for locale handler. */
072: public static final String LOCALE_HANDLER = "class_locale_handler";
073:
074: /** Request parameter to force encoding selection. */
075: public static final String PARAMETER_ENCODING = "__encoding";
076:
077: /** Request parameter to force locale selection. */
078: public static final String PARAMETER_LOCALE = "__locale";
079:
080: /** The log object for this class. */
081: private static final Log LOG = CmsLog
082: .getLog(CmsLocaleManager.class);
083:
084: /** The default locale, this is the first configured locale. */
085: private static Locale m_defaultLocale;
086:
087: /** The set of available locale names. */
088: private List m_availableLocales;
089:
090: /** The default locale names (must be a subset of the available locale names). */
091: private List m_defaultLocales;
092:
093: /** Indicates if the locale manager is fully initialized. */
094: private boolean m_initialized;
095:
096: /** The configured locale handler. */
097: private I_CmsLocaleHandler m_localeHandler;
098:
099: /**
100: * Initializes a new CmsLocaleManager, called from the configuration.<p>
101: */
102: public CmsLocaleManager() {
103:
104: setDefaultLocale();
105: m_availableLocales = new ArrayList();
106: m_defaultLocales = new ArrayList();
107: m_localeHandler = new CmsDefaultLocaleHandler();
108: if (CmsLog.INIT.isInfoEnabled()) {
109: CmsLog.INIT.info(Messages.get().getBundle().key(
110: Messages.INIT_I18N_CONFIG_START_0));
111: }
112: // register this object as event listener
113: OpenCms.addCmsEventListener(this ,
114: new int[] { I_CmsEventListener.EVENT_CLEAR_CACHES });
115: }
116:
117: /**
118: * Initializes a new CmsLocaleManager, used for OpenCms runlevel 1 (unit tests) only.<p>
119: *
120: * @param defaultLocale the default locale to use
121: */
122: public CmsLocaleManager(Locale defaultLocale) {
123:
124: setDefaultLocale();
125: m_initialized = false;
126:
127: m_availableLocales = new ArrayList();
128: m_defaultLocales = new ArrayList();
129: m_localeHandler = new CmsDefaultLocaleHandler();
130:
131: m_defaultLocale = defaultLocale;
132: m_defaultLocales.add(defaultLocale);
133: m_availableLocales.add(defaultLocale);
134: }
135:
136: /**
137: * Required for setting the default locale on the first possible time.<p>
138: */
139: static {
140: setDefaultLocale();
141: }
142:
143: /**
144: * Returns the default locale configured in <code>opencms-system.xml</code>,
145: * that is the first locale from the list provided
146: * in the <code>opencms/system/internationalization/localesdefault</code> node.<p>
147: *
148: * @return the default locale configured in <code>opencms-system.xml</code>
149: */
150: public static Locale getDefaultLocale() {
151:
152: return m_defaultLocale;
153: }
154:
155: /**
156: * Returns a locale created from the given full name.<p>
157: *
158: * The full name must consist of language code,
159: * country code(optional), variant(optional) separated by "_".<p>
160: *
161: * This method will always return a valid Locale!
162: * If the provided locale name is not valid (i.e. leads to an Exception
163: * when trying to create the Locale, then the configured default Locale is returned.<p>
164: *
165: * @param localeName the full locale name
166: * @return the locale or <code>null</code> if not available
167: */
168: public static Locale getLocale(String localeName) {
169:
170: if (CmsStringUtil.isEmpty(localeName)) {
171: return getDefaultLocale();
172: }
173: Locale locale = null;
174: if (OpenCms.getMemoryMonitor() != null) {
175: // this may be used AFTER shutdown
176: locale = OpenCms.getMemoryMonitor().getCachedLocale(
177: localeName);
178: }
179: if (locale != null) {
180: return locale;
181: }
182: try {
183: String[] localeNames = CmsStringUtil.splitAsArray(
184: localeName, '_');
185: locale = new Locale(localeNames[0],
186: (localeNames.length > 1) ? localeNames[1] : "",
187: (localeNames.length > 2) ? localeNames[2] : "");
188: } catch (Throwable t) {
189: LOG
190: .debug(Messages.get().getBundle().key(
191: Messages.LOG_CREATE_LOCALE_FAILED_1,
192: localeName), t);
193: // map this error to the default locale
194: locale = getDefaultLocale();
195: }
196: if (OpenCms.getMemoryMonitor() != null) {
197: // this may be used AFTER shutdown
198: OpenCms.getMemoryMonitor().cacheLocale(localeName, locale);
199: }
200: return locale;
201: }
202:
203: /**
204: * Returns the locale names from the given List of locales as a comma separated String.<p>
205: *
206: * For example, if the input List contains <code>{@link Locale#ENGLISH}</code> and
207: * <code>{@link Locale#GERMANY}</code>, the result will be <code>"en, de_DE"</code>.<p>
208: *
209: * An empty String is returned if the input is <code>null</code>, or contains no elements.<p>
210: *
211: * @param localeNames the locale names to generate a String from
212: *
213: * @return the locale names from the given List of locales as a comma separated String
214: */
215: public static String getLocaleNames(List localeNames) {
216:
217: StringBuffer result = new StringBuffer();
218: if (localeNames != null) {
219: Iterator i = localeNames.iterator();
220: while (i.hasNext()) {
221: result.append(i.next().toString());
222: if (i.hasNext()) {
223: result.append(", ");
224: }
225: }
226: }
227: return result.toString();
228: }
229:
230: /**
231: * Returns a List of locales from an array of locale names.<p>
232: *
233: * @param localeNames array of locale names
234: * @return a List of locales derived from the given locale names
235: */
236: public static List getLocales(List localeNames) {
237:
238: List result = new ArrayList(localeNames.size());
239: for (int i = 0; i < localeNames.size(); i++) {
240: result.add(getLocale(localeNames.get(i).toString().trim()));
241: }
242: return result;
243: }
244:
245: /**
246: * Returns a List of locales from a comma-separated string of locale names.<p>
247: *
248: * @param localeNames a comma-separated string of locale names
249: * @return a List of locales derived from the given locale names
250: */
251: public static List getLocales(String localeNames) {
252:
253: if (localeNames == null) {
254: return null;
255: }
256: return getLocales(CmsStringUtil.splitAsList(localeNames, ','));
257: }
258:
259: /**
260: * Returns the content encoding set for the given resource.<p>
261: *
262: * The content encoding is controlled by the property {@link CmsPropertyDefinition#PROPERTY_CONTENT_ENCODING},
263: * which can be set on the resource or on a parent folder for all resources in this folder.<p>
264: *
265: * In case no encoding has been set, the default encoding from
266: * {@link org.opencms.main.CmsSystemInfo#getDefaultEncoding()} is returned.<p>
267: *
268: * @param cms the current OpenCms user context
269: * @param res the resource to read the encoding for
270: *
271: * @return the content encoding set for the given resource
272: */
273: public static final String getResourceEncoding(CmsObject cms,
274: CmsResource res) {
275:
276: String encoding = null;
277: // get the encoding
278: try {
279: encoding = cms.readPropertyObject(res,
280: CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING,
281: true).getValue();
282: if (encoding != null) {
283: encoding = CmsEncoder.lookupEncoding(encoding.trim(),
284: encoding);
285: }
286: } catch (CmsException e) {
287: if (LOG.isInfoEnabled()) {
288: LOG.info(Messages.get().getBundle().key(
289: Messages.ERR_READ_ENCODING_PROP_1,
290: res.getRootPath()), e);
291: }
292: }
293: if (encoding == null) {
294: encoding = OpenCms.getSystemInfo().getDefaultEncoding();
295: }
296: return encoding;
297: }
298:
299: /**
300: * Sets the default locale of the Java VM to <code>{@link Locale#ENGLISH}</code> if the
301: * current default has any other language then English set.<p>
302: *
303: * This is required because otherwise the default (English) resource bundles
304: * would not be displayed for the English locale if a translated default locale exists.<p>
305: *
306: * Here's an example of how this issues shows up:
307: * On a German server, the default locale usually is <code>{@link Locale#GERMAN}</code>.
308: * All English translations for OpenCms are located in the "default" message files, for example
309: * <code>org.opencms.i18n.message.properties</code>. If the German localization is installed, it will be
310: * located in <code>org.opencms.i18n.message_de.properties</code>. If user has English selected
311: * as his locale, the default Java lookup mechanism first tries to find
312: * <code>org.opencms.i18n.message_en.properties</code>. However, this file does not exist, since the
313: * English localization is kept in the default file. Next, the Java lookup mechanism tries to find the servers
314: * default locale, which in this example is German. Since there is a German message file, the Java lookup mechanism
315: * is finished and uses this German localization, not the default file. Therefore the
316: * user get the German localization, not the English one.
317: * Setting the default locale explicitly to English avoids this issue.<p>
318: */
319: private static void setDefaultLocale() {
320:
321: // set the default locale to english
322: // this is required because otherwise the default (english) resource bundles
323: // would not be displayed for the english locale if a translated locale exists
324:
325: Locale oldLocale = Locale.getDefault();
326: if (!(Locale.ENGLISH.getLanguage().equals(oldLocale
327: .getLanguage()))) {
328: // default language is not English
329: try {
330: Locale.setDefault(Locale.ENGLISH);
331: if (CmsLog.INIT.isInfoEnabled()) {
332: CmsLog.INIT.info(Messages.get().getBundle().key(
333: Messages.INIT_I18N_DEFAULT_LOCALE_2,
334: Locale.ENGLISH, oldLocale));
335: }
336: } catch (Exception e) {
337: // any Exception: the locale has not been changed, so there may be issues with the English
338: // localization but OpenCms will run in general
339: CmsLog.INIT.error(Messages.get().getBundle().key(
340: Messages.LOG_UNABLE_TO_SET_DEFAULT_LOCALE_2,
341: Locale.ENGLISH, oldLocale), e);
342: }
343: } else {
344: if (CmsLog.INIT.isInfoEnabled()) {
345: CmsLog.INIT.info(Messages.get().getBundle().key(
346: Messages.INIT_I18N_KEEPING_DEFAULT_LOCALE_1,
347: oldLocale));
348: }
349: }
350:
351: // initialize the static member with the new default
352: m_defaultLocale = Locale.getDefault();
353: }
354:
355: /**
356: * Adds a locale to the list of available locales.<p>
357: *
358: * @param localeName the locale to add
359: */
360: public void addAvailableLocale(String localeName) {
361:
362: Locale locale = getLocale(localeName);
363: // add full variation (language / country / variant)
364: if (!m_availableLocales.contains(locale)) {
365: m_availableLocales.add(locale);
366: if (CmsLog.INIT.isInfoEnabled()) {
367: CmsLog.INIT
368: .info(Messages.get().getBundle().key(
369: Messages.INIT_I18N_CONFIG_ADD_LOCALE_1,
370: locale));
371: }
372: }
373: // add variation with only language and country
374: locale = new Locale(locale.getLanguage(), locale.getCountry());
375: if (!m_availableLocales.contains(locale)) {
376: m_availableLocales.add(locale);
377: if (CmsLog.INIT.isInfoEnabled()) {
378: CmsLog.INIT
379: .info(Messages.get().getBundle().key(
380: Messages.INIT_I18N_CONFIG_ADD_LOCALE_1,
381: locale));
382: }
383: }
384: // add variation with language only
385: locale = new Locale(locale.getLanguage());
386: if (!m_availableLocales.contains(locale)) {
387: m_availableLocales.add(locale);
388: if (CmsLog.INIT.isInfoEnabled()) {
389: CmsLog.INIT
390: .info(Messages.get().getBundle().key(
391: Messages.INIT_I18N_CONFIG_ADD_LOCALE_1,
392: locale));
393: }
394: }
395: }
396:
397: /**
398: * Adds a locale to the list of default locales.<p>
399: *
400: * @param localeName the locale to add
401: */
402: public void addDefaultLocale(String localeName) {
403:
404: Locale locale = getLocale(localeName);
405: if (!m_defaultLocales.contains(locale)) {
406: m_defaultLocales.add(locale);
407: if (CmsLog.INIT.isInfoEnabled()) {
408: CmsLog.INIT.info(Messages.get().getBundle().key(
409: Messages.INIT_I18N_CONFIG_DEFAULT_LOCALE_2,
410: new Integer(m_defaultLocales.size()), locale));
411:
412: }
413: }
414: }
415:
416: /**
417: * Implements the CmsEvent interface,
418: * the locale manager the events to clear
419: * the list of cached keys .<p>
420: *
421: * @param event CmsEvent that has occurred
422: */
423: public void cmsEvent(CmsEvent event) {
424:
425: switch (event.getType()) {
426: case I_CmsEventListener.EVENT_CLEAR_CACHES:
427: clearCaches();
428: break;
429: default: // no operation
430: }
431: }
432:
433: /**
434: * Returns the list of available {@link Locale}s configured in <code>opencms-system.xml</code>,
435: * in the <code>opencms/system/internationalization/localesconfigured</code> node.<p>
436: *
437: * The list of configured available locales contains all locales that are allowed to be used in the VFS,
438: * for example as languages in XML content files.<p>
439: *
440: * The available locales are a superset of the default locales, see {@link #getDefaultLocales()}.<p>
441: *
442: * It's possible to reduce the system default by setting the propery
443: * <code>{@link CmsPropertyDefinition#PROPERTY_AVAILABLE_LOCALES}</code>
444: * to a comma separated list of locale names. However, you can not add new available locales,
445: * only remove from the configured list.<p>
446: *
447: * @return the list of available locale names, e.g. <code>en, de</code>
448: *
449: * @see #getDefaultLocales()
450: */
451: public List getAvailableLocales() {
452:
453: return m_availableLocales;
454: }
455:
456: /**
457: * Returns an array of available locale names for the given resource.<p>
458: *
459: * @param cms the current cms permission object
460: * @param resourceName the name of the resource
461: *
462: * @return an array of available locale names
463: *
464: * @see #getAvailableLocales()
465: */
466: public List getAvailableLocales(CmsObject cms, String resourceName) {
467:
468: String availableNames = null;
469: try {
470: availableNames = cms.readPropertyObject(resourceName,
471: CmsPropertyDefinition.PROPERTY_AVAILABLE_LOCALES,
472: true).getValue();
473: } catch (CmsException exc) {
474: // noop
475: }
476:
477: List result = null;
478: if (availableNames != null) {
479: result = getAvailableLocales(availableNames);
480: }
481: if ((result == null) || (result.size() == 0)) {
482: return m_availableLocales;
483: } else {
484: return result;
485: }
486: }
487:
488: /**
489: * Returns a List of available locales from a comma separated string of locale names.<p>
490: *
491: * All names are filtered against the allowed available locales
492: * configured in <code>opencms-system.xml</code>.<P>
493: *
494: * @param names a comma-separated String of locale names
495: * @return List of locales created from the given locale names
496: *
497: * @see #getAvailableLocales()
498: */
499: public List getAvailableLocales(String names) {
500:
501: return checkLocaleNames(getLocales(names));
502: }
503:
504: /**
505: * Tries to find the given requested locale (eventually simplified) in the collection of available locales,
506: * if the requested locale is not found it will return the first match from the given list of default locales.<p>
507: *
508: * @param requestedLocale the requested locale, if this (or a simplified version of it) is available it will be returned
509: * @param defaults a list of default locales to use in case the requested locale is not available
510: * @param available the available locales to find a match in
511: *
512: * @return the best matching locale name or null if no name matches
513: */
514: public Locale getBestMatchingLocale(Locale requestedLocale,
515: List defaults, Collection available) {
516:
517: if ((available == null) || available.isEmpty()) {
518: // no locales are available at all
519: return null;
520: }
521:
522: // the requested locale is the match we want to find most
523: if (available.contains(requestedLocale)) {
524: // check if the requested locale is directly available
525: return requestedLocale;
526: }
527: if (requestedLocale.getVariant().length() > 0) {
528: // locale has a variant like "en_EN_whatever", try only with language and country
529: Locale check = new Locale(requestedLocale.getLanguage(),
530: requestedLocale.getCountry(), "");
531: if (available.contains(check)) {
532: return check;
533: }
534: }
535: if (requestedLocale.getCountry().length() > 0) {
536: // locale has a country like "en_EN", try only with language
537: Locale check = new Locale(requestedLocale.getLanguage(),
538: "", "");
539: if (available.contains(check)) {
540: return check;
541: }
542: }
543:
544: // available locales do not match the requested locale
545: if ((defaults == null) || defaults.isEmpty()) {
546: // if we have no default locales we are out of luck
547: return null;
548: }
549:
550: // no match found for the requested locale, return the first match from the default locales
551: return getFirstMatchingLocale(defaults, available);
552: }
553:
554: /**
555: * Returns the "the" default locale for the given resource.<p>
556: *
557: * It's possible to override the system default (see {@link #getDefaultLocale()}) by setting the property
558: * <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> to a comma separated list of locale names.
559: * This property is inherited from the parent folders.
560: * This method will return the first locale from that list.<p>
561: *
562: * The default locale must be contained in the set of configured available locales,
563: * see {@link #getAvailableLocales()}.
564: * In case an invalid locale has been set with the property, this locale is ignored and the
565: * same result as {@link #getDefaultLocale()} is returned.<p>
566: *
567: * In case the property <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> has not been set
568: * on the resource or a parent folder,
569: * this method returns the same result as {@link #getDefaultLocale()}.<p>
570: *
571: * @param cms the current cms permission object
572: * @param resourceName the name of the resource
573: * @return an array of default locale names
574: *
575: * @see #getDefaultLocales()
576: * @see #getDefaultLocales(CmsObject, String)
577: */
578: public Locale getDefaultLocale(CmsObject cms, String resourceName) {
579:
580: List defaultLocales = getDefaultLocales(cms, resourceName);
581: Locale result;
582: if (defaultLocales.size() > 0) {
583: result = (Locale) defaultLocales.get(0);
584: } else {
585: result = getDefaultLocale();
586: }
587: return result;
588: }
589:
590: /**
591: * Returns the list of default {@link Locale}s configured in <code>opencms-system.xml</code>,
592: * in the <code>opencms/system/internationalization/localesdefault</code> node.<p>
593: *
594: * Since the default locale is always available, the result list will always contain at least one Locale.<p>
595: *
596: * It's possible to override the system default by setting the property
597: * <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> to a comma separated list of locale names.
598: * This property is inherited from the parent folders.<p>
599: *
600: * The default locales must be a subset of the configured available locales, see {@link #getAvailableLocales()}.
601: * In case an invalid locale has been set with the property, this locale is ignored.<p>
602: *
603: * The default locale names are used as a fallback mechanism in case a locale is requested
604: * that can not be found, for example when delivering content form an XML content.<p>
605: *
606: * There is a list of default locales (instead of just one default locale) since there
607: * are scenarios when one default is not enough. Consider the following example:<i>
608: * The main default locale is set to "en". An example XML content file contains just one language,
609: * in this case "de" and not "en". Now a request is made to the file for the locale "fr". If
610: * there would be only one default locale ("en"), we would have to give up. But since we allow more then
611: * one default, we can deliver the "de" content instead of a blank page.</I><p>
612: *
613: * @return the list of default locale names, e.g. <code>en, de</code>
614: *
615: * @see #getAvailableLocales()
616: */
617: public List getDefaultLocales() {
618:
619: return m_defaultLocales;
620: }
621:
622: /**
623: * Returns an array of default locales for the given resource.<p>
624: *
625: * Since the default locale is always available, the result list will always contain at least one Locale.<p>
626: *
627: * It's possible to override the system default (see {@link #getDefaultLocales()}) by setting the property
628: * <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> to a comma separated list of locale names.
629: * This property is inherited from the parent folders.<p>
630: *
631: * The default locales must be a subset of the configured available locales, see {@link #getAvailableLocales()}.
632: * In case an invalid locale has been set with the property, this locale is ignored.<p>
633: *
634: * In case the property <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> has not been set
635: * on the resource or a parent folder,
636: * this method returns the same result as {@link #getDefaultLocales()}.<p>
637: *
638: * Use this method in case you need to get all configured default options for a resource,
639: * if you just need the "the" default locale for a resource,
640: * use <code>{@link #getDefaultLocale(CmsObject, String)}</code>.<p>
641: *
642: * @param cms the current cms permission object
643: * @param resource the resource to read the default locale properties for
644: * @return an array of default locale names
645: *
646: * @see #getDefaultLocales()
647: * @see #getDefaultLocale(CmsObject, String)
648: * @see #getDefaultLocales(CmsObject, String)
649: *
650: * @since 7.0.2
651: */
652: public List getDefaultLocales(CmsObject cms, CmsResource resource) {
653:
654: String defaultNames = null;
655: try {
656: defaultNames = cms.readPropertyObject(resource,
657: CmsPropertyDefinition.PROPERTY_LOCALE, true)
658: .getValue();
659: } catch (CmsException e) {
660: LOG.warn(Messages.get().getBundle().key(
661: Messages.ERR_READ_ENCODING_PROP_1,
662: cms.getSitePath(resource)), e);
663: }
664: return getDefaultLocales(defaultNames);
665: }
666:
667: /**
668: * Returns an array of default locales for the given resource.<p>
669: *
670: * Since the default locale is always available, the result list will always contain at least one Locale.<p>
671: *
672: * It's possible to override the system default (see {@link #getDefaultLocales()}) by setting the property
673: * <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> to a comma separated list of locale names.
674: * This property is inherited from the parent folders.<p>
675: *
676: * The default locales must be a subset of the configured available locales, see {@link #getAvailableLocales()}.
677: * In case an invalid locale has been set with the property, this locale is ignored.<p>
678: *
679: * In case the property <code>{@link CmsPropertyDefinition#PROPERTY_LOCALE}</code> has not been set
680: * on the resource or a parent folder,
681: * this method returns the same result as {@link #getDefaultLocales()}.<p>
682: *
683: * Use this method in case you need to get all configured default options for a resource,
684: * if you just need the "the" default locale for a resource,
685: * use <code>{@link #getDefaultLocale(CmsObject, String)}</code>.<p>
686: *
687: * @param cms the current cms permission object
688: * @param resourceName the name of the resource
689: * @return an array of default locale names
690: *
691: * @see #getDefaultLocales()
692: * @see #getDefaultLocale(CmsObject, String)
693: * @see #getDefaultLocales(CmsObject, CmsResource)
694: */
695: public List getDefaultLocales(CmsObject cms, String resourceName) {
696:
697: String defaultNames = null;
698: try {
699: defaultNames = cms.readPropertyObject(resourceName,
700: CmsPropertyDefinition.PROPERTY_LOCALE, true)
701: .getValue();
702: } catch (CmsException e) {
703: LOG
704: .warn(Messages.get().getBundle().key(
705: Messages.ERR_READ_ENCODING_PROP_1,
706: resourceName), e);
707: }
708: return getDefaultLocales(defaultNames);
709: }
710:
711: /**
712: * Returns the first matching locale (eventually simplified) from the available locales.<p>
713: *
714: * In case no match is found, code <code>null</code> is returned.<p>
715: *
716: * @param locales must be an ascending sorted list of locales in order of preference
717: * @param available the available locales to find a match in
718: *
719: * @return the first precise or simplified match, or <code>null</code> in case no match is found
720: */
721: public Locale getFirstMatchingLocale(List locales,
722: Collection available) {
723:
724: Iterator i;
725: // first try a precise match
726: i = locales.iterator();
727: while (i.hasNext()) {
728: Locale locale = (Locale) i.next();
729: if (available.contains(locale)) {
730: // precise match
731: return locale;
732: }
733: }
734:
735: // now try a match only with language and country
736: i = locales.iterator();
737: while (i.hasNext()) {
738: Locale locale = (Locale) i.next();
739: if (locale.getVariant().length() > 0) {
740: // the locale has a variant, try to match without the variant
741: locale = new Locale(locale.getLanguage(), locale
742: .getCountry(), "");
743: if (available.contains(locale)) {
744: // match
745: return locale;
746: }
747: }
748: }
749:
750: // finally try a match only with language
751: i = locales.iterator();
752: while (i.hasNext()) {
753: Locale locale = (Locale) i.next();
754: if (locale.getCountry().length() > 0) {
755: // the locale has a country, try to match without the country
756: locale = new Locale(locale.getLanguage(), "", "");
757: if (available.contains(locale)) {
758: // match
759: return locale;
760: }
761: }
762: }
763:
764: // no match
765: return null;
766: }
767:
768: /**
769: * Returns the the appropriate locale/encoding for a request,
770: * using the "right" locale handler for the given resource.<p>
771: *
772: * Certain system folders (like the Workplace) require a special
773: * locale handler different from the configured handler.
774: * Use this method if you want to resolve locales exactly like
775: * the system does for a request.<p>
776: *
777: * @param req the current http request
778: * @param user the current user
779: * @param project the current project
780: * @param resource the URI of the requested resource (with full site root added)
781: *
782: * @return the i18n information to use for the given request context
783: */
784: public CmsI18nInfo getI18nInfo(HttpServletRequest req,
785: CmsUser user, CmsProject project, String resource) {
786:
787: CmsI18nInfo i18nInfo = null;
788:
789: // check if this is a request against a Workplace folder
790: if (OpenCms.getSiteManager().isWorkplaceRequest(req)) {
791: // The list of configured localized workplace folders
792: List wpLocalizedFolders = OpenCms.getWorkplaceManager()
793: .getLocalizedFolders();
794: for (int i = wpLocalizedFolders.size() - 1; i >= 0; i--) {
795: if (resource.startsWith((String) wpLocalizedFolders
796: .get(i))) {
797: // use the workplace locale handler for this resource
798: i18nInfo = OpenCms.getWorkplaceManager()
799: .getI18nInfo(req, user, project, resource);
800: break;
801: }
802: }
803: }
804: if (i18nInfo == null) {
805: // use default locale handler
806: i18nInfo = m_localeHandler.getI18nInfo(req, user, project,
807: resource);
808: }
809:
810: // check the request for special parameters overriding the locale handler
811: Locale locale = null;
812: String encoding = null;
813: if (req != null) {
814: String localeParam = req
815: .getParameter(CmsLocaleManager.PARAMETER_LOCALE);
816: // check request for parameters
817: if (localeParam != null) {
818: // "__locale" parameter found in request
819: locale = CmsLocaleManager.getLocale(localeParam);
820: }
821: // check for "__encoding" parameter in request
822: encoding = req
823: .getParameter(CmsLocaleManager.PARAMETER_ENCODING);
824: }
825:
826: // merge values from request with values from locale handler
827: if (locale == null) {
828: locale = i18nInfo.getLocale();
829: }
830: if (encoding == null) {
831: encoding = i18nInfo.getEncoding();
832: }
833:
834: // still some values might be "null"
835: if (locale == null) {
836: locale = getDefaultLocale();
837: if (LOG.isDebugEnabled()) {
838: LOG.debug(Messages.get().getBundle().key(
839: Messages.LOG_LOCALE_NOT_FOUND_1, locale));
840: }
841: }
842: if (encoding == null) {
843: encoding = OpenCms.getSystemInfo().getDefaultEncoding();
844: if (LOG.isDebugEnabled()) {
845: LOG.debug(Messages.get().getBundle().key(
846: Messages.LOG_ENCODING_NOT_FOUND_1, encoding));
847: }
848: }
849:
850: // return the merged values
851: return new CmsI18nInfo(locale, encoding);
852: }
853:
854: /**
855: * Returns the configured locale handler.<p>
856: *
857: * This handler is used to derive the appropriate locale/encoding for a request.<p>
858: *
859: * @return the locale handler
860: */
861: public I_CmsLocaleHandler getLocaleHandler() {
862:
863: return m_localeHandler;
864: }
865:
866: /**
867: * Initializes this locale manager with the OpenCms system configuration.<p>
868: *
869: * @param cms an OpenCms context object that must have been initialized with "Admin" permissions
870: */
871: public void initialize(CmsObject cms) {
872:
873: // init the locale handler
874: m_localeHandler.initHandler(cms);
875: // set default locale
876: m_defaultLocale = (Locale) m_defaultLocales.get(0);
877: // set initialized status
878: m_initialized = true;
879: if (CmsLog.INIT.isInfoEnabled()) {
880: CmsLog.INIT.info(Messages.get().getBundle().key(
881: Messages.INIT_I18N_CONFIG_VFSACCESS_0));
882: }
883: }
884:
885: /**
886: * Returns <code>true</code> if this locale manager is fully initialized.<p>
887: *
888: * This is required to prevent errors during unit tests,
889: * simple unit tests will usually not have a fully
890: * initialized locale manager available.<p>
891: *
892: * @return true if the locale manager is fully initialized
893: */
894: public boolean isInitialized() {
895:
896: return m_initialized;
897: }
898:
899: /**
900: * Sets the configured locale handler.<p>
901: *
902: * @param localeHandler the locale handler to set
903: */
904: public void setLocaleHandler(I_CmsLocaleHandler localeHandler) {
905:
906: if (localeHandler != null) {
907: m_localeHandler = localeHandler;
908: }
909: if (CmsLog.INIT.isInfoEnabled()) {
910: CmsLog.INIT.info(Messages.get().getBundle().key(
911: Messages.INIT_I18N_CONFIG_LOC_HANDLER_1,
912: m_localeHandler.getClass().getName()));
913: }
914: }
915:
916: /**
917: * Returns a list of available locale names derived from the given locale names.<p>
918: *
919: * Each name in the given list is checked against the internal hash map of allowed locales,
920: * and is appended to the resulting list only if the locale exists.<p>
921: *
922: * @param locales List of locales to check
923: * @return list of available locales derived from the given locale names
924: */
925: private List checkLocaleNames(List locales) {
926:
927: if (locales == null) {
928: return null;
929: }
930: List result = new ArrayList();
931: Iterator i = locales.iterator();
932: while (i.hasNext()) {
933: Locale locale = (Locale) i.next();
934: if (m_availableLocales.contains(locale)) {
935: result.add(locale);
936: }
937: }
938: return result;
939: }
940:
941: /**
942: * Clears the caches in the locale manager.<p>
943: */
944: private void clearCaches() {
945:
946: // flush all caches
947: OpenCms.getMemoryMonitor().flushLocales();
948: CmsResourceBundleLoader.flushBundleCache();
949:
950: if (LOG.isDebugEnabled()) {
951: LOG.debug(Messages.get().getBundle().key(
952: Messages.LOG_LOCALE_MANAGER_FLUSH_CACHE_1,
953: "EVENT_CLEAR_CACHES"));
954: }
955: }
956:
957: /**
958: * Internal helper, returns an array of default locales for the given default names.<p>
959: *
960: * If required returns the system configured default locales.<p>
961: *
962: * @param defaultNames the default locales to use, can be <code>null</code> or a comma separated list
963: * of locales, for example <code>"en, de"</code>
964: *
965: * @return an array of default locales for the given default names
966: */
967: private List getDefaultLocales(String defaultNames) {
968:
969: List result = null;
970: if (defaultNames != null) {
971: result = getAvailableLocales(defaultNames);
972: }
973: if ((result == null) || (result.size() == 0)) {
974: return getDefaultLocales();
975: } else {
976: return result;
977: }
978: }
979: }
|