DataSourceMessageSource by ultteky

bean id="messageSource" class="com.mycompany.DataSourceMessageSource">
<property name="dataSource"><ref bean="dataSource"/></property>
<property name="tableName"><value>resources</value></property>
<property name="codeColumn"><value>code</value></property>
<property name="languageColumn"><value>language</value></property>
<property name="countryColumn"><value>country</value></property>
<property name="variantColumn"><value>variant</value></property>
<property name="msgColumn"><value>msg</value></property>
<property name="basenameColumn"><value>basename</value></property>
<property name="cacheSeconds"><value>10</value></property>
</bean>







/**
 * MessageSource implementation that reads the messages from a Datasource
 */
public class DataSourceMessageSource extends AbstractMessageSource implements InitializingBean {

  /** the DataSource to use for reading in messages */
  private DataSource dataSource;

  /** used for filtering certain groups of messages (optional) */
  private String[] basenames = new String[0];

  /** 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 String tableName = "resources";
  private String codeColumn = "code";
  private String languageColumn = "language";
  private String countryColumn = "country";
  private String variantColumn = "variant";
  private String msgColumn = "msg";
  private String basenameColumn = "basename";

  private Locale fallbackLocale = Locale.ENGLISH;

  private String baseSql;

  public void init() {
    refreshIfNecessary();
  }

  public void afterPropertiesSet() throws Exception {
    logger.debug ("afterPropertiesSet");
    computeBaseSql();
  }

  /**
   * set the datasource
   * @param dataSource the dataSource to use for loading messages
   */
  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  /**
   * Set a single basename, must match exactly with value in tableName.basenameColumn
   * @param basename the single basename
   * @see #setBasenames
   */
  public void setBasename(String basename) {
    setBasenames(new String[]{basename});
  }

  /**
   * Set an array of basenames
   * @param basenames an array of basenames
   * @see #setBasename
   */
  public void setBasenames(String[] basenames) {
    this.basenames = basenames;
  }

  /**
   * set the name of the table to read messages from
   * @param tableName name of table containing the messages
   */
  public void setTableName(String tableName) {
    this.tableName = tableName;
  }

  /**
   * set the name of the column containing the message code
   * @param codeColumn name of the column containing the message code
   */
  public void setCodeColumn(String codeColumn) {
    this.codeColumn = codeColumn;
  }

  /**
   * set name of language column
   * @param languageColumn name of column containing the language of the message
   */
  public void setLanguageColumn(String languageColumn) {
    this.languageColumn = languageColumn;
  }

  /** set name of the country column <br/>
   * optional: use "null" if your table does not have a country column
   * @param countryColumn name of country column
   */
  public void setCountryColumn(String countryColumn) {
    this.countryColumn = countryColumn;
  }

  /** set name of variant column <br/>
   * optional: use "null" if your table does not have a variant column
   * @param variantColumn
   */
  public void setVariantColumn(String variantColumn) {
    this.variantColumn = variantColumn;
  }

  /** set name of the message column
   * @param msgColumn name of the column containing the localized message
   */
  public void setMsgColumn(String msgColumn) {
    this.msgColumn = msgColumn;
  }

  /** set name of basename column
   * optional: use "null" if your table does not have a basename column
   * @param basenameColumn
   */
  public void setBasenameColumn(String basenameColumn) {
    this.basenameColumn = basenameColumn;
  }

  /**
   * 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();
      }
    }
  }

  /**
   * create a locale from given values, supporting null-values for country and variant
   * @param language language to construct Locale for
   * @param country  country to construct Locale for
   * @param variant variant to construct Locale for
   * @return a Locale object
   * @throws NullPointerException if language is null
   */
  private Locale createLocale (String language, String country, String variant) {
    if (country == null)
      return new Locale(language);
    if (variant == null)
      return new Locale(language,country);
    return new Locale(language,country,variant);
  }

  /**
   * 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, String language, String country, String variant, String message) {
    Locale locale = createLocale(language, country, variant);
    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;
  }

  private void computeBaseSql() {
    StringBuffer sql = new StringBuffer("SELECT ");
    sql.append(codeColumn).append(" as code, ");
    sql.append(languageColumn).append(" as lang, ");
    sql.append(countryColumn).append(" as country, ");
    sql.append(variantColumn).append(" as variant, ");
    sql.append(msgColumn).append(" as msg ");
    sql.append("FROM ").append(tableName);
    baseSql = sql.toString();
  }

  /**
   * 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();
    if (baseSql == null)
      computeBaseSql();
    StringBuffer sql = new StringBuffer(baseSql);
    if (basenames.length > 0) {
      sql.append(" WHERE ").append(basenameColumn).append(" = ?");
    }
    for (int i=1; i < basenames.length; i++) {
      sql.append(" OR ").append(basenameColumn).append(" = ?");
    }
    if (logger.isDebugEnabled()) {
      logger.debug("sql=[" + sql + "]");
    }
    MappingSqlQuery query = new MappingSqlQuery(dataSource, sql.toString()) {
      protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        String code = rs.getString("code");
        String language = rs.getString("lang");
        String country = rs.getString("country");
        String variant = rs.getString("variant");
        String msg = rs.getString("msg");
        mapMessage(code, language, country, variant, msg);
        return null;
      }
    };
    query.setTypes(createIntArray(basenames.length,Types.VARCHAR));
    query.compile();
    query.execute(basenames);
    long millis = System.currentTimeMillis() - startTime;
    logger.info ("readMessages took " + millis + " millis");
  }

  /**
   * create an int array of given length with all elements initialized
   * with given value
   * @param length length of array to create
   * @param value all elements will be set to this value
   * @return an int-array of given length
   */
  private static int[] createIntArray (int length, int value) {
    int result[] = new int[length];
    for (int i=0; i<length; i++) {
      result[i] = value;
    }
    return result;
  }

}


트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://ultteky.egloos.com/tb/10522427 [도움말]

덧글

댓글 입력 영역