/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.util; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.Set; import org.openmrs.GlobalProperty; import org.openmrs.api.GlobalPropertyListener; import org.openmrs.api.context.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; /** * A utility class for working with Locales. */ public class LocaleUtility implements GlobalPropertyListener { private static Logger log = LoggerFactory.getLogger(LocaleUtility.class); /** * Cached version of the default locale. This is cached so that we don't have to look it up in * the global property table every page load */ private static Locale defaultLocaleCache = null; /** * Cached version of the localeAllowedList. This is cached so that we don't have to look it up * in the global property table every page load */ private static List<Locale> localesAllowedListCache = null; /** * Gets the default locale specified as a global property. * * @return default locale object. * @since 1.5 * @should not return null if global property does not exist * @should not fail with empty global property value * @should not fail with bogus global property value * @should return locale object for global property * @should not cache locale when session is not open */ public static Locale getDefaultLocale() { if (defaultLocaleCache == null) { if (Context.isSessionOpen()) { try { String locale = Context.getAdministrationService().getGlobalProperty( OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_LOCALE); if (StringUtils.hasLength(locale)) { try { defaultLocaleCache = fromSpecification(locale); } catch (Exception t) { log.warn("Unable to parse default locale global property value: " + locale, t); } } } catch (Exception e) { // swallow most of the stack trace for most users log.warn("Unable to get locale global property value. " + e.getMessage()); log.trace("Unable to get locale global property value", e); } // if we weren't able to load the locale from the global property, // use the default one if (defaultLocaleCache == null) { defaultLocaleCache = fromSpecification(OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_LOCALE_DEFAULT_VALUE); } } else { // if session is not open, return the default locale without caching return fromSpecification(OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_LOCALE_DEFAULT_VALUE); } } return defaultLocaleCache; } /** * Compatible is a looser matching than that provided by Locale.equals(). Two locales are * considered equal if they are equal, or if either does not have a country specified and the * languages match. * * @param lhs left hand side Locale * @param rhs right hand side Locale * @return true if the two locales are compatible, false otherwise * @should confirm different language missing country as compatible * @should confirm same language missing country as compatible * @should not confirm different country as compatible * @should confirm matching country as compatible * @should not confirm different language as compatible * @should confirm matching language as compatible */ public static boolean areCompatible(Locale lhs, Locale rhs) { if (lhs.equals(rhs)) { return true; } else if ((("".equals(lhs.getCountry())) || ("".equals(rhs.getCountry()))) && lhs.getLanguage().equals(rhs.getLanguage())) { // no country specified, so language match is good enough return true; } return false; } /** * Creates a locale based on a string specification. The specification must be conform with the * following format: ll_CC_vv <br> * <ul> * <li>ll: two-character lowercase ISO-639 language code * <li>CC: two-character uppercase ISO-3166 country code optional * <li>vv: arbitrary length variant code * </ul> * For example: en_US_Traditional_WIN ...represents English language in the United States with * the traditional collation for windows. * * @param localeSpecification encoded locale specification * @return the representative Locale, or null if the specification is invalid * @should get locale from two character language code * @should get locale from language code and country code * @should get locale from language code country code and variant */ public static Locale fromSpecification(String localeSpecification) { Locale createdLocale = null; localeSpecification = localeSpecification.trim(); String[] localeComponents = localeSpecification.split("_"); if (localeComponents.length == 1) { createdLocale = new Locale(localeComponents[0]); } else if (localeComponents.length == 2) { createdLocale = new Locale(localeComponents[0], localeComponents[1]); } else if (localeComponents.length > 2) { String variant = localeSpecification.substring(localeSpecification.indexOf(localeComponents[2])); // gets everything after the // second underscore createdLocale = new Locale(localeComponents[0], localeComponents[1], variant); } return createdLocale; } /** * Utility method that returns a collection of all openmrs system locales, the set includes the * current logged in user's preferred locale if any is set, the default locale, allowed locales * in the order they are specified in the 'allowed.locale.list' global property and 'en' at the * very end of the set if it isn't yet among them. * * @return a collection of all specified and allowed locales with no duplicates. * @should return a set of locales with a predictable order * @should return a set of locales with no duplicates * @should have default locale as the first element if user has no preferred locale * @should have default locale as the second element if user has a preferred locale * @should always have english included in the returned collection * @should always have default locale default value included in the returned collection * @since 1.7 */ public static Set<Locale> getLocalesInOrder() { Set<Locale> locales = new LinkedHashSet<Locale>(); locales.add(Context.getLocale()); locales.add(getDefaultLocale()); if (localesAllowedListCache == null) { localesAllowedListCache = Context.getAdministrationService().getAllowedLocales(); } if (localesAllowedListCache != null) { locales.addAll(localesAllowedListCache); } locales.add(Locale.ENGLISH); locales.add(fromSpecification(OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_LOCALE_DEFAULT_VALUE)); return locales; } public static void setDefaultLocaleCache(Locale defaultLocaleCache) { LocaleUtility.defaultLocaleCache = defaultLocaleCache; } public static void setLocalesAllowedListCache(List<Locale> localesAllowedListCache) { LocaleUtility.localesAllowedListCache = localesAllowedListCache; } @Override public void globalPropertyChanged(GlobalProperty newValue) { // reset the value setDefaultLocaleCache(null); setLocalesAllowedListCache(null); } @Override public void globalPropertyDeleted(String propertyName) { // reset the value setDefaultLocaleCache(null); setLocalesAllowedListCache(null); } @Override public boolean supportsPropertyName(String propertyName) { return propertyName.equals(OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_LOCALE) || propertyName.equals(OpenmrsConstants.GLOBAL_PROPERTY_LOCALE_ALLOWED_LIST); } /** * Checks if specified locale object is valid * * @param locale * object for validation * @return true if locale is available */ public static boolean isValid(Locale locale) { try { return locale.getISO3Language() != null && locale.getISO3Country() != null; } catch (MissingResourceException e) { return false; } } }