com.surveypanel.utils.DBMessageSource.java Source code

Java tutorial

Introduction

Here is the source code for com.surveypanel.utils.DBMessageSource.java

Source

/*
* SurveyPanel
* Copyright (C) 2009 Serge Tan Panza
* All rights reserved.
* License: GNU/GPL License v3 , see LICENSE.txt
* SurveyPanel is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.txt for copyright notices and details.
*/
package com.surveypanel.utils;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import org.springframework.context.support.AbstractMessageSource;

import com.surveypanel.service.I18nManager;

public class DBMessageSource extends AbstractMessageSource {

    private final I18nManager i18nManager;

    /** how long the messages will be cached (before timestamps will be checked) <br/>
     * a value below zero means "cache forever"  */
    private long cacheMillis = -1;

    /** timestamp: when messages were loaded for the last time  */
    private long loadTimestamp = -1;

    /** timestamp: when messages were last updated according to messageReader */
    private long lastUpdate = -1;

    /** Cache holding already generated MessageFormats per message code and Locale <br/>
     * Map <String, Map <Locale, MessageFormat&gt;&gt;  */
    private final Map cachedMessageFormats = new HashMap();

    /** all messages (for all basenames) per locale <br/>
     * Map <Locale, properties&gt;  */
    private Map cachedMergedProperties = new HashMap();

    private Locale fallbackLocale = Locale.ENGLISH;

    private long surveyId;

    public DBMessageSource(I18nManager i18nManager, long surveyId) {
        this.i18nManager = i18nManager;
        this.surveyId = surveyId;
        refreshIfNecessary();
    }

    /**
     * set the Locale to fallback to when no match is found <br/><br/>
     * set to null if you do not want a fallback Locale <br/>
     * default is Locale.ENGLISH
     * @param fallbackLocale the locale to fallback to when no match is found
     */
    public void setFallbackLocale(Locale fallbackLocale) {
        this.fallbackLocale = fallbackLocale;
    }

    /** set number of seconds to cache the messages
     * <ul>
     * <li>Default is "-1", indicating to cache forever
     * <li>A positive number will cache loaded messages for the given number of seconds.
     * This is essentially the interval between refresh attempts.
     * Note that a refresh attempt will first check the last-modified timestamp using getLastUpdate
     * <li>A value of "0" will check the last-modified timestamp on every message access.
     * <b>Do not use this in a production environment!</b>
     * </ul>
     * @see #getLastUpdate
     */
    public void setCacheSeconds(int cacheSeconds) {
        this.cacheMillis = cacheSeconds * 1000;
    }

    /**
     * build an array of alternative locales for the given locale <br/>
     * result does not contain original locale
     * @param locale the locale to find alternatives for
     * @return an array of alternative locales
     */
    private Locale[] getAlternativeLocales(Locale locale) {
        Locale[] locales = new Locale[3];
        int count = 0;
        if (locale.getVariant().length() > 0) {
            // add a locale without the variant
            locales[count] = new Locale(locale.getLanguage(), locale.getCountry());
            count++;
        }
        if (locale.getCountry().length() > 0) {
            // add a locale without the country
            locales[count] = new Locale(locale.getLanguage());
            count++;
        }
        if (fallbackLocale != null) {
            locales[count] = fallbackLocale;
        }
        return locales;
    }

    protected String internalResolveCodeWithoutArguments(String code, Locale locale) {
        refreshIfNecessary();
        String msg = getMessages(locale).getProperty(code);
        if (msg != null)
            return msg;
        Locale[] locales = getAlternativeLocales(locale);
        for (int i = 0; i < locales.length; i++) {
            msg = getMessages(locales[i]).getProperty(code);
            if (msg != null)
                return msg;
        }
        return null;
    }

    protected synchronized String resolveCodeWithoutArguments(String code, Locale locale) {
        String msg = internalResolveCodeWithoutArguments(code, locale);
        if (logger.isDebugEnabled())
            logger.debug("resolved [" + code + "] for locale [" + locale + "] => [" + msg + "]");
        if (msg == null && logger.isInfoEnabled()) {
            logger.info("could not resolve [" + code + "] for locale [" + locale + "]");
        }
        return msg;
    }

    protected synchronized MessageFormat resolveCode(String code, Locale locale) {
        refreshIfNecessary();
        MessageFormat messageFormat = getMessageFormat(code, locale);
        if (messageFormat != null)
            return messageFormat;
        Locale[] locales = getAlternativeLocales(locale);
        for (int i = 0; i < locales.length; i++) {
            messageFormat = getMessageFormat(code, locales[i]);
            if (messageFormat != null)
                return messageFormat;
        }
        if (logger.isInfoEnabled()) {
            logger.info("could not resolve [" + code + "] for locale [" + locale + "]");
        }
        return null;
    }

    protected void refreshIfNecessary() {
        if (loadTimestamp < 0) {
            // loadMessages for the first time
            readFromDataSource();
            this.lastUpdate = getLastUpdate();
            return;
        }
        if (cacheMillis < 0) {
            return;
        }
        if (System.currentTimeMillis() > loadTimestamp + cacheMillis) {
            // time to check if messages have been updated
            long lastUpdate = getLastUpdate();
            if (lastUpdate != this.lastUpdate) {
                // messages have changed => read them in again
                this.lastUpdate = lastUpdate;
                readFromDataSource();
            }
        }
    }

    /**
     * get the messages for the given locale, creating a new Properties object if necessary
     * @param locale the locale to find messages for
     * @return a Properties object
     */
    private Properties getMessages(Locale locale) {
        Properties messages = (Properties) cachedMergedProperties.get(locale);
        if (messages == null) {
            messages = new Properties();
            cachedMergedProperties.put(locale, messages);
        }
        return messages;
    }

    /**
     * stores a message in our internal data structures
     * @param code the code of the message to store
     * @param language the language of the message (required)
     * @param country the country of the message (optional, may be null)
     * @param variant the variant of the message (optional, may be null)
     * @param message the actual message
     */
    public void mapMessage(String code, Locale locale, String message) {
        Properties messages = getMessages(locale);
        if (logger.isDebugEnabled())
            logger.debug("adding message [" + message + "] for code [" + code + "] and locale [" + locale + "]");
        messages.setProperty(code, message);
    }

    protected MessageFormat getMessageFormat(String code, Locale locale) {
        Map localeMap = (Map) this.cachedMessageFormats.get(code);
        if (localeMap != null) {
            MessageFormat result = (MessageFormat) localeMap.get(locale);
            if (result != null) {
                return result;
            }
        }
        String msg = getMessages(locale).getProperty(code);
        if (msg != null) {
            if (localeMap == null) {
                localeMap = new HashMap();
                this.cachedMessageFormats.put(code, localeMap);
            }
            MessageFormat result = createMessageFormat(msg, locale);
            localeMap.put(locale, result);
            return result;
        }
        return null;
    }

    /**
     * This method should return the timestamp when messages were last updated. <br/>
     * The default implementation always returns -1, which will prevent refreshing. <br/>
     * sub-classes should override this method when refreshing is desired
     * @return -1
     */
    public long getLastUpdate() {
        return -1; // never refresh
    }

    private final void readFromDataSource() {
        readMessages();
        loadTimestamp = System.currentTimeMillis();
    }

    protected void readMessages() {
        cachedMergedProperties.clear();
        cachedMessageFormats.clear();
        long startTime = System.currentTimeMillis();

        Map<Locale, Map<String, String>> bundles = i18nManager.getBundles(surveyId);
        for (Locale locale : bundles.keySet()) {
            Map<String, String> keys = bundles.get(locale);
            for (String key : keys.keySet()) {
                mapMessage(key, locale, keys.get(key));
            }
        }

        long millis = System.currentTimeMillis() - startTime;
        logger.info("readMessages took " + millis + " millis");
    }

}