Java tutorial
/* * 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>> */ private final Map cachedMessageFormats = new HashMap(); /** all messages (for all basenames) per locale <br/> * Map <Locale, properties> */ 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"); } }