View Javadoc

1   /*
2    * $Id: PropertyMessageResources.java 421119 2006-07-12 04:49:11Z wsmoak $
3    *
4    * Copyright 1999-2004 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts.util;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.Locale;
29  import java.util.Properties;
30  
31  /***
32   * Concrete subclass of <code>MessageResources</code> that reads message keys
33   * and corresponding strings from named property resources in the same manner
34   * that <code>java.util.PropertyResourceBundle</code> does.  The
35   * <code>base</code> property defines the base property resource name, and
36   * must be specified. <p> <strong>IMPLEMENTATION NOTE</strong> - This class
37   * trades memory for speed by caching all messages located via generalizing
38   * the Locale under the original locale as well. This results in specific
39   * messages being stored in the message cache more than once, but improves
40   * response time on subsequent requests for the same locale + key
41   * combination.
42   *
43   * @version $Rev: 421119 $ $Date: 2005-05-07 12:11:38 -0400 (Sat, 07 May 2005)
44   *          $
45   */
46  public class PropertyMessageResources extends MessageResources {
47      /***
48       * The <code>Log</code> instance for this class.
49       */
50      protected static final Log log =
51          LogFactory.getLog(PropertyMessageResources.class);
52  
53      // ------------------------------------------------------------- Properties
54  
55      /***
56       * The set of locale keys for which we have already loaded messages, keyed
57       * by the value calculated in <code>localeKey()</code>.
58       */
59      protected HashMap locales = new HashMap();
60  
61      /***
62       * The cache of messages we have accumulated over time, keyed by the value
63       * calculated in <code>messageKey()</code>.
64       */
65      protected HashMap messages = new HashMap();
66  
67      // ----------------------------------------------------------- Constructors
68  
69      /***
70       * Construct a new PropertyMessageResources according to the specified
71       * parameters.
72       *
73       * @param factory The MessageResourcesFactory that created us
74       * @param config  The configuration parameter for this MessageResources
75       */
76      public PropertyMessageResources(MessageResourcesFactory factory,
77          String config) {
78          super(factory, config);
79          log.trace("Initializing, config='" + config + "'");
80      }
81  
82      /***
83       * Construct a new PropertyMessageResources according to the specified
84       * parameters.
85       *
86       * @param factory    The MessageResourcesFactory that created us
87       * @param config     The configuration parameter for this
88       *                   MessageResources
89       * @param returnNull The returnNull property we should initialize with
90       */
91      public PropertyMessageResources(MessageResourcesFactory factory,
92          String config, boolean returnNull) {
93          super(factory, config, returnNull);
94          log.trace("Initializing, config='" + config + "', returnNull="
95              + returnNull);
96      }
97  
98      // --------------------------------------------------------- Public Methods
99  
100     /***
101      * Returns a text message for the specified key, for the default Locale. A
102      * null string result will be returned by this method if no relevant
103      * message resource is found for this key or Locale, if the
104      * <code>returnNull</code> property is set.  Otherwise, an appropriate
105      * error message will be returned. <p> This method must be implemented by
106      * a concrete subclass.
107      *
108      * @param locale The requested message Locale, or <code>null</code> for
109      *               the system default Locale
110      * @param key    The message key to look up
111      * @return text message for the specified key and locale
112      */
113     public String getMessage(Locale locale, String key) {
114         if (log.isDebugEnabled()) {
115             log.debug("getMessage(" + locale + "," + key + ")");
116         }
117 
118         // Initialize variables we will require
119         String localeKey = localeKey(locale);
120         String originalKey = messageKey(localeKey, key);
121         String messageKey = null;
122         String message = null;
123         int underscore = 0;
124         boolean addIt = false; // Add if not found under the original key
125 
126         // Loop from specific to general Locales looking for this message
127         while (true) {
128             // Load this Locale's messages if we have not done so yet
129             loadLocale(localeKey);
130 
131             // Check if we have this key for the current locale key
132             messageKey = messageKey(localeKey, key);
133 
134             synchronized (messages) {
135                 message = (String) messages.get(messageKey);
136 
137                 if (message != null) {
138                     if (addIt) {
139                         messages.put(originalKey, message);
140                     }
141 
142                     return (message);
143                 }
144             }
145 
146             // Strip trailing modifiers to try a more general locale key
147             addIt = true;
148             underscore = localeKey.lastIndexOf("_");
149 
150             if (underscore < 0) {
151                 break;
152             }
153 
154             localeKey = localeKey.substring(0, underscore);
155         }
156 
157         // Try the default locale if the current locale is different
158         if (!defaultLocale.equals(locale)) {
159             localeKey = localeKey(defaultLocale);
160             messageKey = messageKey(localeKey, key);
161             loadLocale(localeKey);
162 
163             synchronized (messages) {
164                 message = (String) messages.get(messageKey);
165 
166                 if (message != null) {
167                     messages.put(originalKey, message);
168 
169                     return (message);
170                 }
171             }
172         }
173 
174         // As a last resort, try the default Locale
175         localeKey = "";
176         messageKey = messageKey(localeKey, key);
177         loadLocale(localeKey);
178 
179         synchronized (messages) {
180             message = (String) messages.get(messageKey);
181 
182             if (message != null) {
183                 messages.put(originalKey, message);
184 
185                 return (message);
186             }
187         }
188 
189         // Return an appropriate error indication
190         if (returnNull) {
191             return (null);
192         } else {
193             return ("???" + messageKey(locale, key) + "???");
194         }
195     }
196 
197     // ------------------------------------------------------ Protected Methods
198 
199     /***
200      * Load the messages associated with the specified Locale key.  For this
201      * implementation, the <code>config</code> property should contain a fully
202      * qualified package and resource name, separated by periods, of a series
203      * of property resources to be loaded from the class loader that created
204      * this PropertyMessageResources instance.  This is exactly the same name
205      * format you would use when utilizing the <code>java.util.PropertyResourceBundle</code>
206      * class.
207      *
208      * @param localeKey Locale key for the messages to be retrieved
209      */
210     protected synchronized void loadLocale(String localeKey) {
211         if (log.isTraceEnabled()) {
212             log.trace("loadLocale(" + localeKey + ")");
213         }
214 
215         // Have we already attempted to load messages for this locale?
216         if (locales.get(localeKey) != null) {
217             return;
218         }
219 
220         locales.put(localeKey, localeKey);
221 
222         // Set up to load the property resource for this locale key, if we can
223         String name = config.replace('.', '/');
224 
225         if (localeKey.length() > 0) {
226             name += ("_" + localeKey);
227         }
228 
229         name += ".properties";
230 
231         InputStream is = null;
232         Properties props = new Properties();
233 
234         // Load the specified property resource
235         if (log.isTraceEnabled()) {
236             log.trace("  Loading resource '" + name + "'");
237         }
238 
239         ClassLoader classLoader =
240             Thread.currentThread().getContextClassLoader();
241 
242         if (classLoader == null) {
243             classLoader = this.getClass().getClassLoader();
244         }
245 
246         is = classLoader.getResourceAsStream(name);
247 
248         if (is != null) {
249             try {
250                 props.load(is);
251             } catch (IOException e) {
252                 log.error("loadLocale()", e);
253             } finally {
254                 try {
255                     is.close();
256                 } catch (IOException e) {
257                     log.error("loadLocale()", e);
258                 }
259             }
260             if (log.isTraceEnabled()) {
261                 log.trace("  Loading resource completed");
262             }
263         } else {
264             if (log.isWarnEnabled()) {
265                 log.warn("  Resource "+name+" Not Found.");
266             }
267         }
268 
269 
270         // Copy the corresponding values into our cache
271         if (props.size() < 1) {
272             return;
273         }
274 
275         synchronized (messages) {
276             Iterator names = props.keySet().iterator();
277 
278             while (names.hasNext()) {
279                 String key = (String) names.next();
280 
281                 if (log.isTraceEnabled()) {
282                     log.trace("  Saving message key '"
283                         + messageKey(localeKey, key));
284                 }
285 
286                 messages.put(messageKey(localeKey, key), props.getProperty(key));
287             }
288         }
289     }
290 }