1
2
3
4
5
6
7
8
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 }
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 }
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 ;
163 }
164
165 return bLoaded ;
166
167 }
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 }
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 }
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
355
356
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 }
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
439
440 if( key != null
441 &&
442 key.startsWith( Z_MESSAGE_PREFIX_PATTERN ) ) {
443 retValue = retValue = MessageFormat.format( key, inserts ) ;
444 }
445 else {
446
447
448 ResourceBundle
449 messages = null ;
450
451 try {
452 messages = AstroGridMessage.loadMessages( this.extractSubsystemAcronym() ) ;
453 }
454 catch( Exception ex ){
455 ;
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 }
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 }
505 }
506
507 return buffer.toString() ;
508
509 }
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 }