View Javadoc

1   /*
2    * Copyright (c) 2001 - 2005 ivata limited.
3    * All rights reserved.
4    * -----------------------------------------------------------------------------
5    * ivata groupware may be redistributed under the GNU General Public
6    * License as published by the Free Software Foundation;
7    * version 2 of the License.
8    *
9    * These programs are free software; you can redistribute them and/or
10   * modify them under the terms of the GNU General Public License
11   * as published by the Free Software Foundation; version 2 of the License.
12   *
13   * These programs are distributed in the hope that they will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16   *
17   * See the GNU General Public License in the file LICENSE.txt for more
18   * details.
19   *
20   * If you would like a copy of the GNU General Public License write to
21   *
22   * Free Software Foundation, Inc.
23   * 59 Temple Place - Suite 330
24   * Boston, MA 02111-1307, USA.
25   *
26   *
27   * To arrange commercial support and licensing, contact ivata at
28   *                  http://www.ivata.com/contact.jsp
29   * -----------------------------------------------------------------------------
30   * $Log: MessageResourcesHandling.java,v $
31   * Revision 1.3  2005/04/11 14:46:11  colinmacleod
32   * Added guessing of field label from field name.
33   *
34   * Revision 1.2  2005/04/09 18:04:19  colinmacleod
35   * Changed copyright text to GPL v2 explicitly.
36   *
37   * Revision 1.1  2005/03/10 10:29:39  colinmacleod
38   * Split off from MaskProperties, so the message
39   * resources for labels, etc. can be accessed globally.
40   *
41   * -----------------------------------------------------------------------------
42   */
43  package com.ivata.mask.web.struts.util;
44  
45  import java.util.HashMap;
46  import java.util.List;
47  import java.util.Locale;
48  import java.util.Map;
49  import java.util.Vector;
50  
51  import org.apache.log4j.Logger;
52  import org.apache.struts.taglib.TagUtils;
53  import org.apache.struts.util.MessageResources;
54  
55  import com.ivata.mask.util.StringHandling;
56  import com.ivata.mask.util.SystemException;
57  
58  /***
59   * Methods in this class wrap/extend the message resource system of
60   * <strong>Struts</strong>.
61   *
62   * @since ivata masks 0.5 (2005-01-20)
63   * @author Colin MacLeod
64   * <a href="mailto:colin.macleod@ivata.com">colin.macleod@ivata.com</a>
65   * @version $Revision: 1.3 $
66   */
67  public final class MessageResourcesHandling {
68      /***
69       * This is prepended to fields to get their message resource string.
70       */
71      public static String FIELD_PREFIX = "field.";
72      /***
73       * If <code>true</code>, then the field name is returned for fields which
74       * have not been defined.
75       */
76      private static boolean guessLabel = true;
77      /***
78       * Logger for this class.
79       */
80      private static Logger logger =
81          Logger.getLogger(MessageResourcesHandling.class);
82  
83      /***
84       * Stores all of the message resources, indexed by bundle name or
85       * the vale of the field <code>NULL_BUNDLE</code> for the default bundle.
86       */
87      private static Map messageResourcesMap = new HashMap();
88      /***
89       * Name of the <code>null</code> bundle in the map.
90       */
91      private static final String NULL_BUNDLE = "null_bundle";
92      /***
93       * This is prepended to submit buttons to get their message resource string.
94       */
95      public static String SUBMIT_PREFIX = "submit.";
96      /***
97       * This string is appended to field names to find their titles in the
98       * message resource bundle.
99       */
100     public static String TITLE_SUFFIX = ".title";
101     /***
102      * This string is appended to field names to find their values in the
103      * message resource bundle.
104      */
105     public static String VALUE_SUFFIX = ".value";
106     static {
107         MessageResourcesHandling.registerMessages(null,
108         "com.ivata.groupware.business.ApplicationResources");
109         MessageResourcesHandling.registerMessages("addressBook",
110             "com.ivata.groupware.business.addressbook.ApplicationResources");
111         MessageResourcesHandling.registerMessages("calendar",
112             "com.ivata.groupware.business.calendar.ApplicationResources");
113         MessageResourcesHandling.registerMessages("library",
114             "com.ivata.groupware.business.library.ApplicationResources");
115         MessageResourcesHandling.registerMessages("mail",
116         "com.ivata.groupware.business.mail.ApplicationResources");
117         MessageResourcesHandling.registerMessages("security",
118             "com.ivata.groupware.admin.security.ApplicationResources");
119     }
120     /***
121      * Get the default label for this field. If no label key is specifically
122      * set, the combination of
123      * <code>labelFieldPath + &quot;.label&quot;</code> is tried in the
124      * application resources. Failing that, the same is attempted using the
125      * default path (<code>defaultPath + &quot;.label&quot;</code>).
126      *
127      * @param locale Refer to {@link MessageResources#getMessage}.
128      * @param bundle Refer to {@link MessageResources#getMessage}.
129      * @param fieldName name of the field for which to return the title.
130      * @param labelKey key used to access the title in the resources. If this
131      * is <code>null</code>, a default key is tried.
132      * @param labelArgs arguments used in conjunction with the key, in the
133      * message resources.
134      * @param resourceFieldPath this is the root of the message resource path
135      * searched. This is usually specific to an input mask, or a value object.
136      * @param labelKeySuffix Some fields have multiple label keys. This
137      * suffix is appended to the key when retrieving the localized text from the
138      * application resources file.
139      * @param button buttons have a different default path to field.
140      * <code>true</code> if this field represents a button.
141      * @param mandatory if <code>true</code> an exception is thrown if no
142      * value is found for this combination.
143      * @return title, if this tag has one, otherwise <code>null</code>.
144      * @throws SystemException if the text cannot be retrieved because of a
145      * system failure, or if the field is mandatory and cannot be found.
146      */
147     public static String getDefaultLabel(
148             final Locale locale,
149             final String bundle,
150             final String fieldName,
151             final String labelKey,
152             final List labelArgs,
153             final String resourceFieldPath,
154             final String labelKeySuffix,
155             final boolean button,
156             boolean mandatory)
157             throws SystemException {
158         String labelKeySuffixAppend;
159         if (StringHandling.isNullOrEmpty(labelKeySuffix)) {
160             labelKeySuffixAppend = "";
161         } else {
162             labelKeySuffixAppend = "." + labelKeySuffix;
163         }
164         boolean reallyMandatory = mandatory;
165         if (guessLabel && mandatory) {
166             reallyMandatory = false;
167         }
168         String label = getDefaultMessage(locale, bundle, fieldName, labelKey,
169                 labelArgs, labelKeySuffixAppend, resourceFieldPath, button,
170                 reallyMandatory);
171         if (guessLabel
172                 && StringHandling.isNullOrEmpty(label)
173                 && !StringHandling.isNullOrEmpty(fieldName)) {
174             label = guessLabel(fieldName);
175         }
176         return label;
177     }
178     /***
179      * Guess what the label should be for a given field or class.
180      *
181      * @param fieldName name of the field or class to guess the label for
182      * @return guessed label.
183      */
184     private static String guessLabel(final String fieldName) {
185         // assume hungarian notation - each capital letter means a space
186         // apart from the first letter which is lower case for a field (but
187         // should be the first word)
188         StringBuffer guessedLabel = new StringBuffer();
189         int pos = 0;
190         // first character should be capitalised
191         guessedLabel.append((char)
192                 Character.toUpperCase(fieldName.charAt(pos++)));
193         int len = fieldName.length();
194         boolean lastUpper = true;
195         while (pos < len) {
196             char ch = fieldName.charAt(pos);
197             // see if it is a new word
198             if (Character.isUpperCase(ch)) {
199                 // if last char was also upper case that means an acronym
200                 // - don't add a space in that case
201                 if (!lastUpper) {
202                     guessedLabel.append(" ");
203                 }
204                 lastUpper = true;
205             } else {
206                 lastUpper = false;
207             }
208             guessedLabel.append(ch);
209             ++pos;
210         }
211         return guessedLabel.toString();
212     }
213     /***
214      * Work out the label field path, used to localize label strings.
215      *
216      * @param fieldName name of the field for which to return the title.
217      * @param resourceFieldPathString this is the prefix for all fields within
218      * the application resources.
219      * @param button buttons have a different default path to field.
220      * <code>true</code> if this field represents a button.
221      * @return label field path for this tag.
222      */
223     private static String getDefaultLabelFieldPath(
224             final String fieldName,
225             final String resourceFieldPathString,
226             final boolean button) {
227         StringBuffer labelFieldPath = new StringBuffer();
228         labelFieldPath.append(resourceFieldPathString);
229         // if the field name was set, default anything which was not from that
230         if (!StringHandling.isNullOrEmpty(fieldName)) {
231             if (!labelFieldPath.toString().endsWith(".")
232                     && (labelFieldPath.length() > 0)) {
233                 labelFieldPath.append(".");
234             }
235         }
236         labelFieldPath.append(getDefaultPath(button, fieldName));
237         return labelFieldPath.toString();
238     }
239     /***
240      * Private helper to avoid repetition. This method is used in all of the
241      * <code>getDefault...</code> methods.
242      *
243      * @param locale Refer to {@link MessageResources#getMessage}.
244      * @param bundle Refer to {@link MessageResources#getMessage}.
245      * @param fieldName name of the field for which to return the message.
246      * @param key key used to access the message in the resources. If this
247      * is <code>null</code>, a default key is tried.
248      * @param args arguments used in conjunction with the key, in the
249      * message resources.
250      * @param suffix one of the <code>..._SUFFIX</code> constants from this
251      * class.
252      * @param resourceFieldPath this is the root of the message resource path
253      * searched. This is usually specific to an input mask, or a value object.
254      * @param button buttons have a different default path to field.
255      * <code>true</code> if this field represents a button.
256      * @param mandatory if <code>true</code> an exception is thrown if no
257      * value is found for this combination.
258      * @return text from the message resources.
259      * @throws SystemException if the text cannot be retrieved because of a
260      * system failure, or if the field is mandatory and cannot be found.
261      */
262     private static String getDefaultMessage(
263             final Locale locale,
264             final String bundle,
265             final String fieldName,
266             final String key,
267             final List args,
268             final String suffix,
269             final String resourceFieldPath,
270             final boolean button,
271             boolean mandatory)
272             throws SystemException {
273         List keysTried = new Vector();
274 
275         String defaultPath = getDefaultPath(button, fieldName);
276         String actualKey = key;
277         String message = null;
278 
279         if (actualKey != null) {
280             keysTried.add(actualKey);
281             message = getMessage(bundle,
282                     locale, actualKey, args);
283             if (logger.isDebugEnabled()) {
284                 logger.debug("Got "
285                         + suffix
286                         + "'"
287                         + message
288                         + "' from key '"
289                         + actualKey
290                         + "'");
291             }
292         }
293         if ((message == null) && (!StringHandling.isNullOrEmpty(fieldName))) {
294             actualKey = getDefaultLabelFieldPath(fieldName, resourceFieldPath,
295                     button)
296                 + suffix;
297             keysTried.add(actualKey);
298             // see if there is a resource here
299             message = getMessage(bundle,
300                     locale, actualKey, args);
301             if (logger.isDebugEnabled()) {
302                 logger.debug("Got "
303                         + suffix
304                         + " '"
305                         + message
306                         + "' from field name key '"
307                         + actualKey
308                         + "'");
309             }
310             // if there is no resource with this path, try the default one
311             if (message == null) {
312                 actualKey = defaultPath
313                     + suffix;
314                 keysTried.add(actualKey);
315                 message = getMessage(bundle,
316                         locale,
317                         actualKey,
318                         args);
319                 if (logger.isDebugEnabled()) {
320                     logger.debug("Got "
321                             + suffix
322                             + " '"
323                             + message
324                             + "' from default path key '"
325                             + actualKey
326                             + "'");
327                 }
328             }
329             // these types _need_ to have a key
330             if ((message == null)
331                     && mandatory) {
332                 throw new SystemException("No message resource "
333                         + suffix
334                         + " key found for field name '"
335                         + fieldName
336                         + "', bundle '"
337                         + bundle
338                         + "', resourceFieldPath '"
339                         + resourceFieldPath
340                         + suffix
341                         + "'. Tried these keys: "
342                         + keysTried
343                         + ".");
344             }
345         }
346         return message;
347     }
348     /***
349      * The default message resource path depends on type - buttons have a
350      * different application resource path from other types
351      * @param button buttons have a different default path to field.
352      * <code>true</code> if this field represents a button.
353      * @param fieldName name of the field to default the path for.
354      * @return default path for this field.
355      */
356     private static String getDefaultPath(final boolean button,
357             final String fieldName) {
358         StringBuffer defaultPath;
359         if (button) {
360             defaultPath = new StringBuffer(
361                     MessageResourcesHandling.SUBMIT_PREFIX);
362         } else {
363             defaultPath = new StringBuffer(
364                     MessageResourcesHandling.FIELD_PREFIX);
365         }
366         defaultPath.append(fieldName);
367         return defaultPath.toString();
368     }
369     /***
370      * Get the default title for this field. If no title key is specifically
371      * set, the combination of
372      * <code>labelFieldPath + &quot;.title&quot;</code> is tried in the
373      * application resources. Failing that, the same is attempted using the
374      * default path (<code>defaultPath + &quot;.title&quot;</code>).
375      *
376      * @param locale Refer to {@link MessageResources#getMessage}.
377      * @param bundle Refer to {@link MessageResources#getMessage}.
378      * @param fieldName name of the field for which to return the title.
379      * @param titleKey key used to access the title in the resources. If this
380      * is <code>null</code>, a default key is tried.
381      * @param titleArgs arguments used in conjunction with the key, in the
382      * message resources.
383      * @param resourceFieldPath this is the root of the message resource path
384      * searched. This is usually specific to an input mask, or a value object.
385      * @param button buttons have a different default path to field.
386      * <code>true</code> if this field represents a button.
387      * @param mandatory if <code>true</code> an exception is thrown if no
388      * value is found for this combination.
389      * @return title, if this tag has one, otherwise <code>null</code>.
390      * @throws SystemException if the text cannot be retrieved because of a
391      * system failure, or if the field is mandatory and cannot be found.
392      */
393     public static String getDefaultTitle(
394             final Locale locale,
395             final String bundle,
396             final String fieldName,
397             final String titleKey,
398             final List titleArgs,
399             final String resourceFieldPath,
400             final boolean button,
401             boolean mandatory)
402             throws SystemException {
403         return getDefaultMessage(locale, bundle, fieldName, titleKey, titleArgs,
404                 TITLE_SUFFIX, resourceFieldPath, button, mandatory);
405     }
406     /***
407      * Get the default value for this field. If no value key is specifically
408      * set, the combination of
409      * <code>labelFieldPath + &quot;.value&quot;</code> is tried in the
410      * application resources. Failing that, the same is attempted using the
411      * default path (<code>defaultPath + &quot;.value&quot;</code>).
412      *
413      * @param locale Refer to {@link MessageResources#getMessage}.
414      * @param bundle Refer to {@link MessageResources#getMessage}.
415      * @param fieldName name of the field for which to return the title.
416      * @param valueKey key used to access the title in the resources. If this
417      * is <code>null</code>, a default key is tried.
418      * @param valueArgs arguments used in conjunction with the key, in the
419      * message resources.
420      * @param resourceFieldPath this is the root of the message resource path
421      * searched. This is usually specific to an input mask, or a value object.
422      * @param button buttons have a different default path to field.
423      * <code>true</code> if this field represents a button.
424      * @param mandatory if <code>true</code> an exception is thrown if no
425      * value is found for this combination.
426      * @return title, if this tag has one, otherwise <code>null</code>.
427      * @throws SystemException if the text cannot be retrieved because of a
428      * system failure, or if the field is mandatory and cannot be found.
429      */
430     public static String getDefaultValue(
431             final Locale locale,
432             final String bundle,
433             final String fieldName,
434             final String valueKey,
435             final List valueArgs,
436             final String resourceFieldPath,
437             final boolean button,
438             boolean mandatory)
439             throws SystemException {
440         return getDefaultMessage(locale, bundle, fieldName, valueKey, valueArgs,
441                 VALUE_SUFFIX, resourceFieldPath, button, mandatory);
442     }
443 
444     /***
445      * This first tries using the
446      * bundle you provided. If that returns <code>null</code>, it tries again
447      * without a bundle.
448      * @param bundle Refer to {@link MessageResources#getMessage}.
449      * @param locale Refer to {@link MessageResources#getMessage}.
450      * @param key Refer to {@link MessageResources#getMessage}.
451      * @param argsList Will be converted to an array of objects. Refer to
452      * {@link MessageResources#getMessage}.
453      *
454      * @return Refer to {@link MessageResources#getMessage}.
455      * @throws SystemException Refer to {@link MessageResources#getMessage}.
456      * @see TagUtils#getInstance
457      */
458     public static String getMessage(final String bundle,
459             final Locale locale, final String key, final List argsList)
460             throws SystemException {
461         Object [] args;
462         if (argsList == null) {
463             args = new Object[] {
464             };
465         } else {
466             args = argsList.toArray();
467         }
468         return getMessage(bundle, locale, key, args);
469     }
470 
471     /***
472      * This first tries using the
473      * bundle you provided. If that returns <code>null</code>, it tries again
474      * without a bundle.
475      * @param bundle Refer to {@link MessageResources#getMessage}.
476      * @param locale Refer to {@link MessageResources#getMessage}.
477      * @param key Refer to {@link MessageResources#getMessage}.
478      * @param args Refer to {@link MessageResources#getMessage}.
479      *
480      * @return Refer to {@link MessageResources#getMessage}.
481      * @throws SystemException Refer to {@link MessageResources#getMessage}.
482      * @see TagUtils#getInstance
483      */
484     public static String getMessage(final String bundle,
485             final Locale locale, final String key, final Object[] args)
486             throws SystemException {
487         assert (key != null);
488         String message = null;
489         if (bundle != null) {
490             MessageResources messages = getMessages(bundle);
491             message = messages.getMessage(locale, key, args);
492         }
493         if (message == null) {
494             MessageResources messages = getMessages(null);
495             message = messages.getMessage(locale, key, args);
496         }
497         return message;
498     }
499     /***
500      * This first tries using the
501      * bundle you provided. If that returns <code>null</code>, it tries again
502      * without a bundle.
503      * @param bundle Refer to {@link MessageResources#getMessage}.
504      * @param locale Refer to {@link MessageResources#getMessage}.
505      * @param key Refer to {@link MessageResources#getMessage}.
506      * @param args Will be converted to an array of objects. Refer to
507      * {@link MessageResources#getMessage}.
508      *
509      * @return Refer to {@link MessageResources#getMessage}.
510      * @throws SystemException Refer to {@link MessageResources#getMessage}.
511      * @see TagUtils#getInstance
512      */
513     public static String getMessage(final String bundle,
514             final String locale, final String key, final List args)
515             throws SystemException {
516         return getMessage(bundle, new Locale(locale), key, args.toArray());
517     }
518     /***
519      * This first tries using the
520      * bundle you provided. If that returns <code>null</code>, it tries again
521      * without a bundle.
522      * @param bundle Refer to {@link MessageResources#getMessage}.
523      * @param locale Refer to {@link MessageResources#getMessage}.
524      * @param key Refer to {@link MessageResources#getMessage}.
525      * @param args Refer to {@link MessageResources#getMessage}.
526      *
527      * @return Refer to {@link MessageResources#getMessage}.
528      * @throws SystemException Refer to {@link MessageResources#getMessage}.
529      * @see TagUtils#getInstance
530      */
531     public static String getMessage(final String bundle,
532             final String locale, final String key, final Object[] args)
533             throws SystemException {
534         return getMessage(bundle, new Locale(locale), key, args);
535     }
536 
537     /***
538      * Get a bundle with the given name.
539      * @param bundle message resources identifier - see <code>Struts</code>
540      * docu.
541      * @return resources message resources instance.
542      * @throws SystemException if the message resources are undefined.
543      */
544     public static MessageResources getMessages(String bundle)
545             throws SystemException {
546         if (bundle == null) {
547             bundle = NULL_BUNDLE;
548         }
549         MessageResources messageResources = (MessageResources )
550                 messageResourcesMap.get(bundle);
551         if (messageResources == null) {
552             throw new SystemException("Resource bundle '"
553                     + bundle
554                     + "' is undefined.");
555         }
556         return messageResources;
557     }
558     /***
559      * @return Returns the guessLabel.
560      */
561     public static boolean isGuessLabel() {
562         return guessLabel;
563     }
564 
565     /***
566      * Register resources for a given bundle.
567      * @param bundle message resources identifier - see <code>Struts</code>
568      * docu.
569      * @param fullPath full path to the resources
570      */
571     public static synchronized void registerMessages(String bundle,
572             String fullPath) {
573         assert (fullPath != null);
574 
575         if (bundle == null) {
576             bundle = NULL_BUNDLE;
577         }
578         MessageResources messageResources =
579             MessageResources.getMessageResources(fullPath);
580         assert (messageResources != null);
581         messageResourcesMap.put(bundle, messageResources);
582     }
583     /***
584      * Refer to {@link #getguessLabel}.
585      * @param guessLabelParam Refer to {@link #getguessLabel}.
586      */
587     public static synchronized void setGuessLabel(boolean guessLabelParam) {
588         if (logger.isDebugEnabled()) {
589             logger.debug("Setting guessLabel. Before '" + guessLabel
590                     + "', after '" + guessLabelParam + "'");
591         }
592         guessLabel = guessLabelParam;
593     }
594 
595     /***
596      * Private default constructor enforces utility class handling.
597      */
598     private MessageResourcesHandling() {
599     }
600 }