/* Copyright (2006-2012) Schibsted ASA * This file is part of Possom. * * Possom 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 3 of the License, or * (at your option) any later version. * * Possom is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Possom. If not, see <http://www.gnu.org/licenses/>. */ package no.sesat.search.site.config; import java.text.MessageFormat; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantReadWriteLock; import no.sesat.commons.ioc.ContextWrapper; import no.sesat.search.site.Site; import no.sesat.search.site.SiteContext; import org.apache.log4j.Logger; /** Wrapper to the MessageResources for a corresponding site. * The site to use is defined through the context. * * * * @version <tt>$Id$</tt> */ public final class TextMessages { public interface Context extends SiteContext, PropertiesContext { }; // Constants ----------------------------------------------------- private static final Logger LOG = Logger.getLogger(TextMessages.class); private static final String MESSAGE_RESOURCE = "messages"; private static final Map<Site,TextMessages> INSTANCES = new HashMap<Site,TextMessages>(); private static final ReentrantReadWriteLock INSTANCES_LOCK = new ReentrantReadWriteLock(); private static final String DEBUG_LOADING_WITH_LOCALE = "Looking for " + MESSAGE_RESOURCE + "_"; private static final String INFO_USING_DEFAULT_LOCALE = " is falling back to the default locale "; // Static -------------------------------------------------------- /** Find the correct instance handling this Site. * * @param cxt * @return * * @todo rename to instanceOf */ public static TextMessages valueOf(final Context cxt) { final Site site = cxt.getSite(); TextMessages instance; try{ INSTANCES_LOCK.readLock().lock(); instance = INSTANCES.get(site); }finally{ INSTANCES_LOCK.readLock().unlock(); } if (instance == null) { instance = new TextMessages(cxt); } return instance; } /** * Utility wrapper to the instanceOf(Context). * @param site * @return * * @todo rename to instanceOf */ public static TextMessages valueOf(final Site site) { // TextMessages.Context for this site & UrlResourceLoader. final TextMessages tm = TextMessages.valueOf(new TextMessages.Context() { public Site getSite() { return site; } public PropertiesLoader newPropertiesLoader( final SiteContext siteCxt, final String resource, final Properties properties) { return UrlResourceLoader.newPropertiesLoader(siteCxt, resource, properties); } }); return tm; } // Attributes ---------------------------------------------------- private final Context context; private final Properties keys = new Properties(); // Constructors -------------------------------------------------- private TextMessages(final Context cxt) { LOG.trace("TextMessages(cxt)"); try{ INSTANCES_LOCK.writeLock().lock(); context = cxt; // import browser-applicable text messages loadKeys(cxt.getSite().getLocale()); // import messages from site's preferred locale [will not override already loaded messages] final SiteConfiguration siteConf = SiteConfiguration.instanceOf(ContextWrapper.wrap(SiteConfiguration.Context.class, cxt)); final String defaultLocale = siteConf.getProperty(SiteConfiguration.SITE_LOCALE_DEFAULT); assert null != defaultLocale : SiteConfiguration.SITE_LOCALE_DEFAULT + " null in " + context.getSite() + ' ' + siteConf.getProperties(); final String[] prefLocale = defaultLocale.split("_"); switch(prefLocale.length){ case 1: LOG.info(cxt.getSite()+INFO_USING_DEFAULT_LOCALE + prefLocale[0]); loadKeys(new Locale(prefLocale[0])); break; case 2: LOG.info(cxt.getSite()+INFO_USING_DEFAULT_LOCALE + prefLocale[0] + '_' + prefLocale[1]); loadKeys(new Locale(prefLocale[0],prefLocale[1])); break; case 3: LOG.info(cxt.getSite()+INFO_USING_DEFAULT_LOCALE + prefLocale[0] + '_' + prefLocale[1] + '_' + prefLocale[2]); loadKeys(new Locale(prefLocale[0],prefLocale[1],prefLocale[2])); break; } INSTANCES.put(cxt.getSite(),this); }finally{ INSTANCES_LOCK.writeLock().unlock(); } } // Public -------------------------------------------------------- /** Does this message exist. * * @param key the message key * @return true if a message value exists */ public boolean hasMessage(final String key) { return keys.containsKey(key); } /** Get the message. * * @param key the message key. * @return Get the message. */ public String getMessage(final String key) { return getMessageImpl(key); } /** Get the message. * * @param key the message key. * @param arg0 first parameter value, eg {0} * @return Get the message. */ public String getMessage(final String key, final Object arg0) { return getMessageImpl(key, arg0); } /** Get the message. * * @param key key the message key. * @param arg0 first parameter value, eg {0} * @param arg1 second parameter value, eg {{1} * @return Get the message. */ public String getMessage(final String key, final Object arg0, final Object arg1) { return getMessageImpl(key, arg0, arg1); } /** Get the message. * * @param key the message key. * @param arg0 first param eter value, eg {0} * @param arg1 second parameter value, eg {1} * @param arg2 third parameter value, eg {2} * @return Get the message. */ public String getMessage(final String key, final Object arg0, final Object arg1, final Object arg2) { return getMessageImpl(key, arg0, arg1, arg2); } /** Get the message. * * @param key the message key. * @param arg0 first parameter value, eg {0} * @param arg1 second parameter value, eg {1} * @param arg2 third parameter value, eg {2} * @param arg3 fourth parameter value, eg {3} * @return Get the message. */ public String getMessage( final String key, final Object arg0, final Object arg1, final Object arg2, final Object arg3) { return getMessageImpl(key, arg0, arg1, arg3); } /** Get the message. * * @param key the message key. * @param arguments variable array of parameters, eg {0}, {1}, {2}, ... * @return Get the message. */ public String getMessage(final String key, final Object... arguments){ return getMessageImpl(key, arguments); } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- private void loadKeys(final Locale l) { try { // import the variant-specific text messages [does not override existing values] LOG.debug(DEBUG_LOADING_WITH_LOCALE + l.getLanguage() + "_" + l.getCountry() + "_" + l.getVariant()); performLoadKeys(l); }catch (ExecutionException ex) { // permissable to not find variant-specific messages LOG.info("Failed to find any variant-specific messages", ex); } try { // import the country-specific text messages [does not override existing values] LOG.debug(DEBUG_LOADING_WITH_LOCALE + l.getLanguage() + "_" + l.getCountry()); performLoadKeys(new Locale(l.getLanguage(), l.getCountry())); }catch (ExecutionException ex) { // permissable to not find country-specific messages LOG.info("Failed to find any country-specific messages", ex); } try { // import the language-specifix text messages [does not override existing values] LOG.debug(DEBUG_LOADING_WITH_LOCALE + l.getLanguage()); performLoadKeys(new Locale(l.getLanguage())); }catch (ExecutionException ex) { // this is not permissable. throw ex.getCause() instanceof ResourceLoadException ? (ResourceLoadException)ex.getCause() : new RuntimeException(ex); } } private void performLoadKeys(final Locale locale) throws ExecutionException{ final PropertiesLoader loader = context.newPropertiesLoader( context, MESSAGE_RESOURCE + "_" + locale.toString() + ".properties", keys); loader.abut(); } private String getMessageImpl(final String key, final Object... arguments){ if(key == null || key.trim().length() == 0){ return ""; }else{ // XXX Struts caches the MessageFormats. Is constructing a MessageFormat really slower than synchronization? final String pattern = keys.getProperty(key); if(pattern == null){ // make it visible that this key is not being localised! return "KEY: " + key; }else{ final MessageFormat format = new MessageFormat(pattern, context.getSite().getLocale()); return format.format(arguments); } } } }