View Javadoc
1   /*
2    * $Id$
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts.util;
22  
23  import java.io.Serializable;
24  import java.text.MessageFormat;
25  import java.util.HashMap;
26  import java.util.Locale;
27  
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * General purpose abstract class that describes an API for retrieving
33   * Locale-sensitive messages from underlying resource locations of an
34   * unspecified design, and optionally utilizing the <code>MessageFormat</code>
35   * class to produce internationalized messages with parametric replacement.
36   * <p> Calls to <code>getMessage()</code> variants without a
37   * <code>Locale</code> argument are presumed to be requesting a message string
38   * in the default <code>Locale</code> for this JVM. <p> Calls to
39   * <code>getMessage()</code> with an unknown key, or an unknown
40   * <code>Locale</code> will return <code>null</code> if the
41   * <code>returnNull</code> property is set to <code>true</code>.  Otherwise, a
42   * suitable error message will be returned instead. <p> <strong>IMPLEMENTATION
43   * NOTE</strong> - Classes that extend this class must be Serializable so that
44   * instances may be used in distributable application server environments.
45   *
46   * @version $Rev$ $Date: 2005-08-29 23:57:50 -0400 (Mon, 29 Aug 2005)
47   *          $
48   */
49  public abstract class MessageResources implements Serializable {
50      private static final long serialVersionUID = -7091558627339276086L;
51  
52      // ------------------------------------------------------------- Properties
53  
54      /**
55       * The {@code Log} instance for this class.
56       */
57      private transient final Logger log =
58          LoggerFactory.getLogger(MessageResources.class);
59  
60      // --------------------------------------------------------- Static Methods
61  
62      /**
63       * The default MessageResourcesFactory used to create MessageResources
64       * instances.
65       */
66      protected static MessageResourcesFactory defaultFactory = null;
67  
68      /**
69       * The configuration parameter used to initialize this MessageResources.
70       */
71      protected String config = null;
72  
73      /**
74       * The default Locale for our environment.
75       */
76      protected Locale defaultLocale = Locale.getDefault();
77  
78      /**
79       * The <code>MessageResourcesFactory</code> that created this instance.
80       */
81      protected MessageResourcesFactory factory = null;
82  
83      /**
84       * The set of previously created MessageFormat objects, keyed by the key
85       * computed in <code>messageKey()</code>.
86       */
87      protected HashMap<String, MessageFormat> formats = new HashMap<>();
88  
89      /**
90       * Indicate is a <code>null</code> is returned instead of an error message
91       * string when an unknown Locale or key is requested.
92       */
93      protected boolean returnNull = false;
94  
95      /**
96       * Indicates whether 'escape processing' should be performed on the error
97       * message string.
98       */
99      private boolean escape = true;
100 
101     // ----------------------------------------------------------- Constructors
102 
103     /**
104      * Construct a new MessageResources according to the specified
105      * parameters.
106      *
107      * @param factory The MessageResourcesFactory that created us
108      * @param config  The configuration parameter for this MessageResources
109      */
110     public MessageResources(MessageResourcesFactory factory, String config) {
111         this(factory, config, false);
112     }
113 
114     /**
115      * Construct a new MessageResources according to the specified
116      * parameters.
117      *
118      * @param factory    The MessageResourcesFactory that created us
119      * @param config     The configuration parameter for this
120      *                   MessageResources
121      * @param returnNull The returnNull property we should initialize with
122      */
123     public MessageResources(MessageResourcesFactory factory, String config,
124         boolean returnNull) {
125         super();
126         this.factory = factory;
127         this.config = config;
128         this.returnNull = returnNull;
129     }
130 
131     /**
132      * The configuration parameter used to initialize this MessageResources.
133      *
134      * @return parameter used to initialize this MessageResources
135      */
136     public String getConfig() {
137         return (this.config);
138     }
139 
140     /**
141      * The <code>MessageResourcesFactory</code> that created this instance.
142      *
143      * @return <code>MessageResourcesFactory</code> that created instance
144      */
145     public MessageResourcesFactory getFactory() {
146         return (this.factory);
147     }
148 
149     /**
150      * Indicates that a <code>null</code> is returned instead of an error
151      * message string if an unknown Locale or key is requested.
152      *
153      * @return true if null is returned if unknown key or locale is requested
154      */
155     public boolean getReturnNull() {
156         return (this.returnNull);
157     }
158 
159     /**
160      * Indicates that a <code>null</code> is returned instead of an error
161      * message string if an unknown Locale or key is requested.
162      *
163      * @param returnNull true Indicates that a <code>null</code> is returned
164      *                   if an unknown Locale or key is requested.
165      */
166     public void setReturnNull(boolean returnNull) {
167         this.returnNull = returnNull;
168     }
169 
170     /**
171      * Indicates whether 'escape processing' should be performed on the error
172      * message string.
173      *
174      * @since Struts 1.2.8
175      */
176     public boolean isEscape() {
177         return escape;
178     }
179 
180     /**
181      * Set whether 'escape processing' should be performed on the error
182      * message string.
183      *
184      * @since Struts 1.2.8
185      */
186     public void setEscape(boolean escape) {
187         this.escape = escape;
188     }
189 
190     // --------------------------------------------------------- Public Methods
191 
192     /**
193      * Returns a text message for the specified key, for the default Locale.
194      *
195      * @param key The message key to look up
196      */
197     public String getMessage(String key) {
198         return this.getMessage((Locale) null, key, null);
199     }
200 
201     /**
202      * Returns a text message after parametric replacement of the specified
203      * parameter placeholders.
204      *
205      * @param key  The message key to look up
206      * @param args An array of replacement parameters for placeholders
207      */
208     public String getMessage(String key, Object[] args) {
209         return this.getMessage((Locale) null, key, args);
210     }
211 
212     /**
213      * Returns a text message after parametric replacement of the specified
214      * parameter placeholders.
215      *
216      * @param key  The message key to look up
217      * @param arg0 The replacement for placeholder {0} in the message
218      */
219     public String getMessage(String key, Object arg0) {
220         return this.getMessage((Locale) null, key, arg0);
221     }
222 
223     /**
224      * Returns a text message after parametric replacement of the specified
225      * parameter placeholders.
226      *
227      * @param key  The message key to look up
228      * @param arg0 The replacement for placeholder {0} in the message
229      * @param arg1 The replacement for placeholder {1} in the message
230      */
231     public String getMessage(String key, Object arg0, Object arg1) {
232         return this.getMessage((Locale) null, key, arg0, arg1);
233     }
234 
235     /**
236      * Returns a text message after parametric replacement of the specified
237      * parameter placeholders.
238      *
239      * @param key  The message key to look up
240      * @param arg0 The replacement for placeholder {0} in the message
241      * @param arg1 The replacement for placeholder {1} in the message
242      * @param arg2 The replacement for placeholder {2} in the message
243      */
244     public String getMessage(String key, Object arg0, Object arg1, Object arg2) {
245         return this.getMessage((Locale) null, key, arg0, arg1, arg2);
246     }
247 
248     /**
249      * Returns a text message after parametric replacement of the specified
250      * parameter placeholders.
251      *
252      * @param key  The message key to look up
253      * @param arg0 The replacement for placeholder {0} in the message
254      * @param arg1 The replacement for placeholder {1} in the message
255      * @param arg2 The replacement for placeholder {2} in the message
256      * @param arg3 The replacement for placeholder {3} in the message
257      */
258     public String getMessage(String key, Object arg0, Object arg1, Object arg2,
259         Object arg3) {
260         return this.getMessage((Locale) null, key, arg0, arg1, arg2, arg3);
261     }
262 
263     /**
264      * Returns a text message for the specified key, for the default Locale. A
265      * null string result will be returned by this method if no relevant
266      * message resource is found for this key or Locale, if the
267      * <code>returnNull</code> property is set.  Otherwise, an appropriate
268      * error message will be returned. <p> This method must be implemented by
269      * a concrete subclass.
270      *
271      * @param locale The requested message Locale, or <code>null</code> for
272      *               the system default Locale
273      * @param key    The message key to look up
274      */
275     public abstract String getMessage(Locale locale, String key);
276 
277     /**
278      * Returns a text message after parametric replacement of the specified
279      * parameter placeholders.  A null string result will be returned by this
280      * method if no resource bundle has been configured.
281      *
282      * @param locale The requested message Locale, or <code>null</code> for
283      *               the system default Locale
284      * @param key    The message key to look up
285      * @param args   An array of replacement parameters for placeholders
286      */
287     public String getMessage(Locale locale, String key, Object[] args) {
288         // Cache MessageFormat instances as they are accessed
289         if (locale == null) {
290             locale = defaultLocale;
291         }
292 
293         MessageFormat format = null;
294         String formatKey = messageKey(locale, key);
295 
296         synchronized (formats) {
297             format = formats.get(formatKey);
298 
299             if (format == null) {
300                 String formatString = getMessage(locale, key);
301 
302                 if (formatString == null) {
303                     return returnNull ? null : ("???" + formatKey + "???");
304                 }
305 
306                 format = new MessageFormat(escape(formatString));
307                 format.setLocale(locale);
308                 formats.put(formatKey, format);
309             }
310         }
311 
312         return format.format(args);
313     }
314 
315     /**
316      * Returns a text message after parametric replacement of the specified
317      * parameter placeholders.  A null string result will never be returned by
318      * this method.
319      *
320      * @param locale The requested message Locale, or <code>null</code> for
321      *               the system default Locale
322      * @param key    The message key to look up
323      * @param arg0   The replacement for placeholder {0} in the message
324      */
325     public String getMessage(Locale locale, String key, Object arg0) {
326         return this.getMessage(locale, key, new Object[] { arg0 });
327     }
328 
329     /**
330      * Returns a text message after parametric replacement of the specified
331      * parameter placeholders.  A null string result will never be returned by
332      * this method.
333      *
334      * @param locale The requested message Locale, or <code>null</code> for
335      *               the system default Locale
336      * @param key    The message key to look up
337      * @param arg0   The replacement for placeholder {0} in the message
338      * @param arg1   The replacement for placeholder {1} in the message
339      */
340     public String getMessage(Locale locale, String key, Object arg0, Object arg1) {
341         return this.getMessage(locale, key, new Object[] { arg0, arg1 });
342     }
343 
344     /**
345      * Returns a text message after parametric replacement of the specified
346      * parameter placeholders.  A null string result will never be returned by
347      * this method.
348      *
349      * @param locale The requested message Locale, or <code>null</code> for
350      *               the system default Locale
351      * @param key    The message key to look up
352      * @param arg0   The replacement for placeholder {0} in the message
353      * @param arg1   The replacement for placeholder {1} in the message
354      * @param arg2   The replacement for placeholder {2} in the message
355      */
356     public String getMessage(Locale locale, String key, Object arg0,
357         Object arg1, Object arg2) {
358         return this.getMessage(locale, key, new Object[] { arg0, arg1, arg2 });
359     }
360 
361     /**
362      * Returns a text message after parametric replacement of the specified
363      * parameter placeholders.  A null string result will never be returned by
364      * this method.
365      *
366      * @param locale The requested message Locale, or <code>null</code> for
367      *               the system default Locale
368      * @param key    The message key to look up
369      * @param arg0   The replacement for placeholder {0} in the message
370      * @param arg1   The replacement for placeholder {1} in the message
371      * @param arg2   The replacement for placeholder {2} in the message
372      * @param arg3   The replacement for placeholder {3} in the message
373      */
374     public String getMessage(Locale locale, String key, Object arg0,
375         Object arg1, Object arg2, Object arg3) {
376         return this.getMessage(locale, key,
377             new Object[] { arg0, arg1, arg2, arg3 });
378     }
379 
380     /**
381      * Return <code>true</code> if there is a defined message for the
382      * specified key in the system default locale.
383      *
384      * @param key The message key to look up
385      */
386     public boolean isPresent(String key) {
387         return this.isPresent(null, key);
388     }
389 
390     /**
391      * Return <code>true</code> if there is a defined message for the
392      * specified key in the specified Locale.
393      *
394      * @param locale The requested message Locale, or <code>null</code> for
395      *               the system default Locale
396      * @param key    The message key to look up
397      */
398     public boolean isPresent(Locale locale, String key) {
399         String message = getMessage(locale, key);
400 
401         if (message == null) {
402             return false;
403         } else if (message.startsWith("???") && message.endsWith("???")) {
404             return false; // FIXME - Only valid for default implementation
405         } else {
406             return true;
407         }
408     }
409 
410     // ------------------------------------------------------ Protected Methods
411 
412     /**
413      * Escape any single quote characters that are included in the specified
414      * message string.
415      *
416      * @param string The string to be escaped
417      */
418     protected String escape(String string) {
419         if (!isEscape()) {
420             return string;
421         }
422 
423         if ((string == null) || (string.indexOf('\'') < 0)) {
424             return string;
425         }
426 
427         int n = string.length();
428         StringBuilder sb = new StringBuilder(n);
429 
430         for (int i = 0; i < n; i++) {
431             char ch = string.charAt(i);
432 
433             if (ch == '\'') {
434                 sb.append('\'');
435             }
436 
437             sb.append(ch);
438         }
439 
440         return sb.toString();
441     }
442 
443     /**
444      * Compute and return a key to be used in caching information by a Locale.
445      * <strong>NOTE</strong> - The locale key for the default Locale in our
446      * environment is a zero length String.
447      *
448      * @param locale The locale for which a key is desired
449      */
450     protected String localeKey(Locale locale) {
451         return (locale == null) ? "" : locale.toString();
452     }
453 
454     /**
455      * Compute and return a key to be used in caching information by Locale
456      * and message key.
457      *
458      * @param locale The Locale for which this format key is calculated
459      * @param key    The message key for which this format key is calculated
460      */
461     protected String messageKey(Locale locale, String key) {
462         return (localeKey(locale) + "." + key);
463     }
464 
465     /**
466      * Compute and return a key to be used in caching information by locale
467      * key and message key.
468      *
469      * @param localeKey The locale key for which this cache key is calculated
470      * @param key       The message key for which this cache key is
471      *                  calculated
472      */
473     protected String messageKey(String localeKey, String key) {
474         return (localeKey + "." + key);
475     }
476 
477     /**
478      * Create and return an instance of <code>MessageResources</code> for the
479      * created by the default <code>MessageResourcesFactory</code>.
480      *
481      * @param config Configuration parameter for this message bundle.
482      */
483     public synchronized static MessageResources getMessageResources(
484         String config) {
485         if (defaultFactory == null) {
486             defaultFactory = MessageResourcesFactory.createFactory();
487         }
488 
489         return defaultFactory.createResources(config);
490     }
491 
492     /**
493      * Log a message to the Writer that has been configured for our use.
494      *
495      * @param message The message to be logged
496      */
497     public void log(String message) {
498         log.debug(message);
499     }
500 
501     /**
502      * Log a message and exception to the Writer that has been configured for
503      * our use.
504      *
505      * @param message   The message to be logged
506      * @param throwable The exception to be logged
507      */
508     public void log(String message, Throwable throwable) {
509         log.debug(message, throwable);
510     }
511 }