View Javadoc

1   /*
2    * @(#)AstroGridMessage.java   1.0
3    *
4    * Copyright (C) AstroGrid. All rights reserved.
5    *
6    * This software is published under the terms of the AstroGrid
7    * Software License version 1.2, a copy of which has been included
8    * with this distribution in the LICENSE.txt file.
9    *
10   */
11  package org.astrogrid.i18n;
12  
13  import java.text.MessageFormat;
14  import java.util.MissingResourceException;
15  import java.util.ResourceBundle;
16  import java.util.Locale;
17  import java.util.Hashtable ;
18  
19  import org.astrogrid.Configurator ;
20  import org.astrogrid.AstroGridException ;
21  
22  import org.astrogrid.log.Log;
23  
24  /***
25   * The <code>AstroGridMessage</code> class represents a message in a way
26   * amenable to internationalization.
27   * <p>
28   * It utilizes the technique of the java.text.MessageFormat class to merge
29   * inserts into a message template. The message templates are held in
30   * properties file(s) which can be produced on a per language basis
31   * or even a per country basis.
32   * <p>
33   * The basic AstroGrid file is ASTROGRID_datacentermessages.properties
34   * Each properties file is loaded as a resource bundle.
35   * <p>
36   * The philosophy on messages is two-fold:
37   * <p>
38   * (1) It is assumed an installation has a default language. Such might
39   * be used for displaying logging messages to system administrators. This
40   * should be loaded at static initialization time by a major AstroGrid
41   * component (in this instance the DatasetAgent of the datacenter).
42   * If messages are required on this basis, use the toString() method.
43   * <p>
44   * (2) However, astronomers/customers are likely to be from many countries.
45   * If messages are required on such a basis, then pass the appropriate
46   * language resource bundle to the toString(ResourceBundle) method.
47   *
48   * @author  Jeff Lusted
49   * @version 1.0 28-May-2003
50   * @since   AstroGrid 1.2
51   *
52   * @see     org.astrogrid.Component
53   * @see     java.text.MessageFormat
54   * @see     java.util.ResourceBundle
55   */
56  public class AstroGridMessage {
57  
58      public static final String
59      /*** Messages category within the component's configuration */
60          MESSAGES_CATEGORY     = "MESSAGES" ,
61      /*** Key within the component's configuration which helps identify the appropriate
62       *  language ResourceBundle. */
63          MESSAGES_BASENAME     = "INSTALLATION.BASENAME" ,
64      /*** Key within the component's configuration which helps identify the appropriate
65       *  language ResourceBundle. */
66          MESSAGES_LANGUAGECODE = "INSTALLATION.LANGUAGECODE" ,
67      /*** Key within the component's configuration which helps identify the appropriate
68       *  language ResourceBundle. */
69          MESSAGES_COUNTRYCODE  = "INSTALLATION.COUNTRYCODE" ,
70      /*** Key within the component's configuration which helps identify the appropriate
71       *  language ResourceBundle. */
72          MESSAGES_VARIANTCODE  = "INSTALLATION.VARIANT" ;
73  
74      private static final String
75          SUBSYSTEM_KEY= "SUBSYSTEM" ;
76  
77     /*** These messages are ones hard-coded in the program. */
78     private static final String
79          ASTROGRIDERROR_MESSAGEKEY_NULL =
80             "AG{0}Z00003:AstroGridMessage: Message key is null",
81          ASTROGRIDERROR_MESSAGE_PATTERN_OR_INSERTS_INVALID =
82             "AG{0}Z00004:AstroGridMessage: Message pattern or its inserts are invalid for message with key [{1}]",
83          ASTROGRIDERROR_MESSAGE_NOT_FOUND_IN_RESOURCEBUNDLE =
84             "AG{0}Z00005:AstroGridMessage: Message with key [{1}] not found in ResourceBundle [{2}]" ,
85          ASTROGRIDERROR_MESSAGE_RESOURCEBUNDLE_NOTFOUND =
86             "AG{0}Z00006:{1}: Message ResourceBundle [{2}] not found in classpath" ;
87  
88      private static final String
89          Z_MESSAGE_PREFIX_PATTERN = "AG{0}Z" ;
90  
91      private static final int
92          SYSTEMACRONYM_STARTINDEX = 2,
93          SYSTEMACRONYM_ENDINDEX = 5 ;
94  
95      /*** The installation default language resource bundles.
96       *  There can be one per AstroGrid subsystem. That means if two subsystems
97       *  are running in the same VM, there will be two. */
98      private static Hashtable
99          resourceBundles = new Hashtable() ;
100 
101    /*** AstroGridMessage key
102     *  @see the ASTROGRID_jesmessages.properties file
103     *    @see java.text.MessageFormat                       */
104    private String
105       key ;
106 
107    /*** Any inserts that may be required by the message.
108     *  Null if message has no inserts.
109     *
110     *  @see java.text.MessageFormat
111     *                                                     */
112    private Object[]
113        inserts ;
114 
115     private static class ResourceBundleKey {
116 
117         private String key ;
118 
119         ResourceBundleKey( String subsystemAcronym ) {
120             key = subsystemAcronym ;
121         }
122 
123         ResourceBundleKey( String subsystemAcronym, Locale locale ) {
124             key = subsystemAcronym + locale.toString() ;
125         }
126 
127         public int hashCode(){ return key.hashCode() ; }
128 
129     } // end of class ResourceBundleKey
130 
131 
132    /***
133       *
134       * Establishes the default language for an installation.
135       * <p>
136       * Must only be called during the static initialization
137       * of a component (eg: DatasetAgent), and thereafter not changed!
138       * Otherwise toString() will not be thread safe.
139       *
140       **/
141    public static void setMessageResource ( String subsystemAcronym, ResourceBundle aResourceBundle  ) {
142       Log.trace( "setMessageResource(): entry") ;
143 
144       try {
145          resourceBundles.put( subsystemAcronym, aResourceBundle ) ;
146         }
147       finally {
148          Log.trace( "setMessageResource(): exit") ;
149       }
150 
151    } // end of setMessageResource()
152 
153 
154    public static boolean isMessageResourceLoaded( String subsystemAcronym ) {
155       boolean
156           bLoaded = false ;
157 
158       try {
159          bLoaded = resourceBundles.containsKey( subsystemAcronym.toUpperCase() ) ;
160       }
161       catch( Exception ex){
162          ; // Do nothing. Any problems and we default to false.
163       }
164 
165       return bLoaded ;
166 
167    } // end of isMessageResourceLoaded()
168 
169 
170     /***
171       *
172       * <p>
173       *
174       **/
175     public static ResourceBundle loadMessages( String subsystemAcronym )
176            throws AstroGridException {
177 
178         String
179             language = Configurator.getProperty( subsystemAcronym
180                                                , MESSAGES_LANGUAGECODE
181                                                , MESSAGES_CATEGORY) ,
182             country = Configurator.getProperty( subsystemAcronym
183                                               , MESSAGES_COUNTRYCODE
184                                               , MESSAGES_CATEGORY) ,
185             variant = Configurator.getProperty( subsystemAcronym
186                                               , MESSAGES_VARIANTCODE
187                                               , MESSAGES_CATEGORY) ;
188 
189         Locale
190             locale = new Locale( language, country, variant ) ;
191 
192         Log.logDebug( "language[" + language + "]\t" +
193                       "country[" + country + "]\t" +
194                       "variant[" + variant + "]\t" +
195                       "locale[" + locale.toString() + "]"  ) ;
196 
197         return AstroGridMessage.loadMessages( subsystemAcronym
198                                             , locale ) ;
199 
200     } // end of loadMessages()
201 
202 
203     /***
204       *
205       * <p>
206       *
207       **/
208     public static synchronized ResourceBundle loadMessages( String subsystemAcronym, Locale locale )
209                   throws AstroGridException {
210         Log.trace( "loadMessages(): entry") ;
211 
212         ResourceBundle
213             resourceBundle = null ;
214 
215         try {
216             ResourceBundleKey
217                key =  new ResourceBundleKey( subsystemAcronym, locale ) ;
218             resourceBundle = (ResourceBundle)resourceBundles.get( key ) ;
219 
220             if( resourceBundle == null ) {
221 
222                 String
223                     messageBundleBaseName = Configurator.getProperty( subsystemAcronym
224                                                                     , MESSAGES_BASENAME
225                                                                     , MESSAGES_CATEGORY) ;
226 
227                 resourceBundle = ResourceBundle.getBundle( messageBundleBaseName, locale ) ;
228                 resourceBundles.put( key, resourceBundle ) ;
229             }
230 
231 
232         }
233         catch( MissingResourceException mrex ) {
234             AstroGridMessage
235                message = new AstroGridMessage( ASTROGRIDERROR_MESSAGE_RESOURCEBUNDLE_NOTFOUND
236                                              , subsystemAcronym
237                                              , Configurator.getClassName( AstroGridMessage.class )
238                                              , locale.toString() ) ;
239             throw new AstroGridException( message ) ;
240         }
241         finally {
242             Log.trace( "loadMessages(): exit") ;
243         }
244 
245         return resourceBundle ;
246 
247     } // end of loadMessages()
248 
249 
250    /***
251       *
252       * Convenience constructor for message with no inserts.
253       * <p>
254       * @param key - message key
255       **/
256    public AstroGridMessage( String key ) {
257       this.key = key ;
258       this.inserts = null ;
259    }
260 
261 
262    /***
263       *
264       * Convenience constructor for message with one insert.
265       * <p>
266       * @param key - message key
267       * @param insert1 - first message insert
268       **/
269    public AstroGridMessage( String key, Object insert1 ) {
270       this.key = key ;
271       this.inserts = new Object[] {insert1} ;
272    }
273 
274 
275    /***
276       *
277       * Convenience constructor for message with two inserts.
278       * <p>
279       * @param key - message key
280       * @param insert1 - first message insert
281       * @param insert2 - second message insert
282       **/
283    public AstroGridMessage( String key, Object insert1, Object insert2 ) {
284       this(key, new Object[] {insert1,insert2} ) ;
285    }
286 
287 
288    /***
289       *
290       * Convenience constructor for message with three inserts.
291       * <p>
292       * @param key - message key
293       * @param insert1 - first message insert
294       * @param insert2 - second message insert
295       * @param insert3 - third message insert
296       **/
297    public AstroGridMessage( String key, Object insert1, Object insert2, Object insert3 ) {
298       this(key, new Object[] {insert1,insert2,insert3} ) ;
299    }
300 
301 
302     /***
303        *
304        * Convenience constructor for message with four inserts.
305        * <p>
306        * @param key - message key
307        * @param insert1 - first message insert
308        * @param insert2 - second message insert
309        * @param insert3 - third message insert
310        * @param insert4 - fourth message insert
311        **/
312     public AstroGridMessage( String key, Object insert1, Object insert2, Object insert3, Object insert4 ) {
313         this(key, new Object[] {insert1,insert2,insert3,insert4} ) ;
314     }
315 
316    /***
317       *
318       * Generalized constructor for message with any number of inserts.
319       * <p>
320       * @param key - message key
321       * @param inserts - array of message inserts
322       **/
323     public AstroGridMessage( String key, Object[] inserts ) {
324        this.key = key ;
325        this.inserts = inserts ;
326     }
327 
328    /***
329       *
330       * A resource bundle can be passed across with any message.
331       * The bundle will represent the language that it is desired
332       * the message be 'translated' into.
333       * <p>
334       * For example, a datacenter may be a French installation
335       * and have its admin messages (for logging purposes) printed
336       * out in French, but its customers may be any nationality.
337       * If it is desired that astronomers get messages in their
338       * own language rather than the installation default, then
339       * the appropriate language resource bundle must be passed
340       * in to this method.
341       * <p>
342       * @param mssgs - a language-specific ResourceBundle
343       * @return - a language-specific string corresponding to
344       *           this message.
345       **/
346    public String toString( ResourceBundle mssgs ) {
347       Log.trace( "toString(ResourceBundle): entry") ;
348 
349       String
350          retValue = null,
351          messageString = null ;
352 
353       try {
354          // Note we concatinate the key (the astrogrid message number)
355          // with the message to produce the format
356          // <message number>:<message text>
357          messageString = key + mssgs.getString( key ) ;
358          retValue = MessageFormat.format( messageString, inserts ) ;
359       }
360       catch( NullPointerException npex ) {
361             Object[]
362                oa = new Object[0] ;
363             retValue = generateErrantMessage( mssgs
364                                             , ASTROGRIDERROR_MESSAGEKEY_NULL
365                                             , oa ) ;
366          Log.logDebug( retValue ) ;
367       }
368       catch( MissingResourceException mrex ) {
369          Object[]
370             oa = { key, mssgs.getLocale().toString() } ;
371             retValue = generateErrantMessage( mssgs
372                                             , ASTROGRIDERROR_MESSAGE_NOT_FOUND_IN_RESOURCEBUNDLE
373                                             , oa ) ;
374 
375          Log.logDebug( retValue ) ;
376       }
377       catch( IllegalArgumentException iaex ) {
378          Object[]
379             oa = { key } ;
380             retValue = generateErrantMessage( mssgs
381                                             , ASTROGRIDERROR_MESSAGE_PATTERN_OR_INSERTS_INVALID
382                                             , oa ) ;
383          Log.logDebug( retValue ) ;
384       }
385       finally {
386          Log.trace( "toString(ResourceBundle): exit") ;
387       }
388 
389       return retValue ;
390 
391    } // end of toString(ResourceBundle)
392 
393 
394     public String toString( Locale locale ) {
395 
396         ResourceBundle
397            resourceBundle = null ;
398         String
399            retValue = null ;
400 
401         try {
402             resourceBundle = AstroGridMessage.loadMessages( this.extractSubsystemAcronym()
403                                                           , locale ) ;
404             retValue = this.toString( resourceBundle ) ;
405         }
406         catch( AstroGridException agex ) {
407             retValue = agex.getAstroGridMessage().toString() ;
408         }
409 
410         return retValue ;
411 
412     }
413 
414 
415 
416    /***
417     *
418     * The default toString() uses the statically defined resource
419     * bundle, which is assumed to be the installation language default for
420     * messages. For example, a datacenter may be a French installation
421     * and have its admin messages (for logging purposes) printed out in French.
422     * <p>
423     * If no installation language default haa been set up, invoking this
424     * method will result in a simple concatination of message key with
425     * any inserts which have been passed across with the message.
426     *
427     * @return - a string hopefully corresponding to the message in
428     *           the installation's default language
429    **/
430     public String toString() {
431       Log.trace( "toString(): entry") ;
432 
433        String
434           retValue = null ;
435 
436       try {
437 
438            // If this is a hard coded Z-style message, go straight for message formatting
439            // without trying to load the message from a ResourceBundle...
440            if( key != null
441                &&
442                key.startsWith( Z_MESSAGE_PREFIX_PATTERN ) ) {
443                retValue = retValue = MessageFormat.format( key, inserts ) ;
444             }
445             else {
446 
447                // Attempt to get the message from the appropriate messages ResourceBundle...
448                ResourceBundle
449                    messages = null ;
450 
451             try {
452                messages = AstroGridMessage.loadMessages( this.extractSubsystemAcronym() ) ;
453             }
454             catch( Exception ex ){
455                ; // This is a fail safe, so we do nothing!
456             }
457 
458             if( messages != null ) {
459                 retValue = this.toString( messages );
460             }
461             else {
462                 retValue  = concatinateKeyAndInserts() ;
463             }
464 
465             }
466 
467       } finally {
468           Log.trace( "toString(): exit") ;
469       }
470 
471        return retValue ;
472 
473     } // end toString()
474 
475 
476     /***
477       * Concatinates message key with message inserts, plus suitable wrappers.
478       * <p>
479       * This is a simple fail-safe mechanism to ensure something relatively
480       * intelligable emerges in those instances where a message cannot be
481       * located in a suitable properties file.
482       *
483       * @return - A simple concatination of message key and inserts
484       */
485    private String concatinateKeyAndInserts() {
486 
487       StringBuffer
488         buffer = new StringBuffer(64) ;
489       buffer
490         .append( "Message[")
491         .append( key )
492         .append( "]  " );
493 
494       if( inserts != null ) {
495 
496          for( int i=0; i < inserts.length; i++ ) {
497            buffer
498             .append( "Insert")
499             .append( i+1 )
500             .append( "[")
501             .append( inserts[i].toString() )
502             .append( "]  ") ;
503 
504          } // end for
505       }
506 
507       return buffer.toString() ;
508 
509     } // end of concatinateKeyAndInserts()
510 
511 
512     private String generateErrantMessage( ResourceBundle mssgs, String message, Object[] inserts ) {
513         String
514            retValue = null ;
515         Object[]
516            insertsPlus = new Object[ inserts.length + 1 ] ;
517         System.arraycopy( inserts, 0, insertsPlus, 1, inserts.length ) ;
518         String
519            subsystemAcronym = mssgs.getString( SUBSYSTEM_KEY ) ;
520         if( subsystemAcronym == null || subsystemAcronym.equals("") ) {
521             subsystemAcronym = "..." ;
522         }
523         insertsPlus[0] = subsystemAcronym ;
524         retValue = MessageFormat.format( message, insertsPlus ) ;
525 
526         return retValue ;
527     }
528 
529 
530     private String extractSubsystemAcronym() {
531         return this.key.substring( SYSTEMACRONYM_STARTINDEX
532                                  , SYSTEMACRONYM_ENDINDEX ) ;
533     }
534 
535 
536 } // end of class AstroGridMessage