/** * Copyright (C) 2008-2010, Squale Project - http://www.squale.org * * This file is part of Squale. * * Squale 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 any later version. * * Squale 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 General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Squale. If not, see <http://www.gnu.org/licenses/>. */ package org.squale.welcom.struts.message; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.util.MessageResources; import org.apache.struts.util.MessageResourcesFactory; /** * Concrete subclass of <code>MessageResources</code> that reads message keys and corresponding strings from named * property resources in the same manner that <code>java.util.PropertyResourceBundle</code> does. The * <code>base</code> property defines the base property resource name, and must be specified. * <p> * <strong>IMPLEMENTATION NOTE</strong> - This class trades memory for speed by caching all messages located via * generalizing the Locale under the original locale as well. This results in specific messages being stored in the * message cache more than once, but improves response time on subsequent requests for the same locale + key * combination. * * @author Craig R. McClanahan * @author David Graham * @version $Revision: 1.8 $ $Date: 2003/04/19 19:06:02 $ */ public class PropertyMessageResources extends MessageResources { // ----------------------------------------------------------- Constructors /** * Construct a new PropertyMessageResources according to the specified parameters. * * @param factory The MessageResourcesFactory that created us * @param config The configuration parameter for this MessageResources */ public PropertyMessageResources( MessageResourcesFactory factory, String config ) { super( factory, config ); log.info( "Initializing, config='" + config + "'" ); } /** * Construct a new PropertyMessageResources according to the specified parameters. * * @param factory The MessageResourcesFactory that created us * @param config The configuration parameter for this MessageResources * @param returnNull The returnNull property we should initialize with */ public PropertyMessageResources( MessageResourcesFactory factory, String config, boolean returnNull ) { super( factory, config, returnNull ); log.info( "Initializing, config='" + config + "', returnNull=" + returnNull ); } // ------------------------------------------------------------- Properties /** * The set of locale keys for which we have already loaded messages, keyed by the value calculated in * <code>localeKey()</code>. */ protected HashMap locales = new HashMap(); /** * The <code>Log</code> instance for this class. */ protected static final Log log = LogFactory.getLog( PropertyMessageResources.class ); /** * The cache of messages we have accumulated over time, keyed by the value calculated in <code>messageKey()</code>. */ protected HashMap messages = new HashMap(); // --------------------------------------------------------- Public Methods /** * Returns a text message for the specified key, for the default Locale. A null string result will be returned by * this method if no relevant message resource is found for this key or Locale, if the <code>returnNull</code> * property is set. Otherwise, an appropriate error message will be returned. * <p> * This method must be implemented by a concrete subclass. * * @param locale The requested message Locale, or <code>null</code> for the system default Locale * @param key The message key to look up * @return text message for the specified key and locale */ public String getMessage( Locale locale, String key ) { if ( log.isDebugEnabled() ) { log.debug( "getMessage(" + locale + "," + key + ")" ); } // Initialize variables we will require String localeKey = localeKey( locale ); String originalKey = messageKey( localeKey, key ); String messageKey = null; String message = null; int underscore = 0; boolean addIt = false; // Add if not found under the original key // Loop from specific to general Locales looking for this message while ( true ) { // Load this Locale's messages if we have not done so yet loadLocale( localeKey ); // Check if we have this key for the current locale key messageKey = messageKey( localeKey, key ); synchronized ( messages ) { message = (String) messages.get( messageKey ); if ( message != null ) { if ( addIt ) { messages.put( originalKey, message ); } return ( message ); } } // Strip trailing modifiers to try a more general locale key addIt = true; underscore = localeKey.lastIndexOf( "_" ); if ( underscore < 0 ) { break; } localeKey = localeKey.substring( 0, underscore ); } /* * // Try the default locale if the current locale is different if (!defaultLocale.equals(locale)) { localeKey = * localeKey(defaultLocale); messageKey = messageKey(localeKey, key); loadLocale(localeKey); synchronized * (messages) { message = (String) messages.get(messageKey); if (message != null) { messages.put(originalKey, * message); return (message); } } } // As a last resort, try the default Locale localeKey = ""; messageKey = * messageKey(localeKey, key); loadLocale(localeKey); synchronized (messages) { message = (String) * messages.get(messageKey); if (message != null) { messages.put(originalKey, message); return (message); } } */ // Return an appropriate error indication if ( returnNull ) { return ( null ); } else { return ( "???" + messageKey( locale, key ) + "???" ); } } // ------------------------------------------------------ Protected Methods /** * Load the messages associated with the specified Locale key. For this implementation, the <code>config</code> * property should contain a fully qualified package and resource name, separated by periods, of a series of * property resources to be loaded from the class loader that created this PropertyMessageResources instance. This * is exactly the same name format you would use when utilizing the <code>java.util.PropertyResourceBundle</code> * class. * * @param localeKey Locale key for the messages to be retrieved */ protected synchronized void loadLocale( String localeKey ) { if ( log.isTraceEnabled() ) { log.trace( "loadLocale(" + localeKey + ")" ); } // Have we already attempted to load messages for this locale? if ( locales.get( localeKey ) != null ) { return; } locales.put( localeKey, localeKey ); // Set up to load the property resource for this locale key, if we can String name = config.replace( '.', '/' ); if ( localeKey.length() > 0 ) { name += "_" + localeKey; } name += ".properties"; InputStream is = null; Properties props = new Properties(); // Load the specified property resource if ( log.isTraceEnabled() ) { log.trace( " Loading resource '" + name + "'" ); } ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if ( classLoader == null ) { classLoader = this.getClass().getClassLoader(); } is = classLoader.getResourceAsStream( name ); if ( is != null ) { try { props.load( is ); } catch ( IOException e ) { log.error( "loadLocale()", e ); } finally { try { is.close(); } catch ( IOException e ) { log.error( "loadLocale()", e ); } } } if ( log.isTraceEnabled() ) { log.trace( " Loading resource completed" ); } // Copy the corresponding values into our cache if ( props.size() < 1 ) { return; } synchronized ( messages ) { Iterator names = props.keySet().iterator(); while ( names.hasNext() ) { String key = (String) names.next(); if ( log.isTraceEnabled() ) { log.trace( " Saving message key '" + messageKey( localeKey, key ) ); } messages.put( messageKey( localeKey, key ), props.getProperty( key ) ); } } } }