001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.i18n; 029 030import org.opencms.main.CmsLog; 031import org.opencms.util.CmsDateUtil; 032import org.opencms.util.CmsStringUtil; 033 034import java.text.DateFormat; 035import java.text.MessageFormat; 036import java.util.Date; 037import java.util.Locale; 038import java.util.MissingResourceException; 039import java.util.ResourceBundle; 040import java.util.function.Function; 041 042import org.apache.commons.logging.Log; 043 044/** 045 * Reads localized resource Strings from a <code>java.util.ResourceBundle</code> 046 * and provides convenience methods to access the Strings from a template.<p> 047 * 048 * This class is frequently used from JSP templates. Because of that, throwing of 049 * exceptions related to the access of the resource bundle are suppressed 050 * so that a template always execute. The class provides an {@link #isInitialized()} method 051 * that can be checked to see if the instance was properly initialized.<p> 052 * 053 * @since 6.0.0 054 */ 055public class CmsMessages { 056 057 /** The suffix of a "short" localized key name. */ 058 public static final String KEY_SHORT_SUFFIX = ".short"; 059 060 /** Prefix / Suffix for unknown keys. */ 061 public static final String UNKNOWN_KEY_EXTENSION = "???"; 062 063 /** The log object for this class. */ 064 private static final Log LOG = CmsLog.getLog(CmsMessages.class); 065 066 /** The resource bundle base name this object was initialized with. */ 067 private String m_bundleName; 068 069 /** The locale to use for looking up the messages from the bundle. */ 070 private Locale m_locale; 071 072 /** The resource bundle this message object was initialized with. */ 073 private ResourceBundle m_resourceBundle; 074 075 /** 076 * Constructor for the messages with an initialized <code>java.util.Locale</code>. 077 * 078 * @param bundleName the base ResourceBundle name 079 * @param locale the m_locale to use, eg. "de", "en" etc. 080 */ 081 public CmsMessages(String bundleName, Locale locale) { 082 083 try { 084 m_locale = locale; 085 m_bundleName = bundleName; 086 m_resourceBundle = CmsResourceBundleLoader.getBundle(bundleName, m_locale); 087 } catch (MissingResourceException e) { 088 m_resourceBundle = null; 089 } catch (Exception e) { 090 m_resourceBundle = null; 091 LOG.error("Error creating messages for bundle " + bundleName + " for locale " + m_locale); 092 } 093 } 094 095 /** 096 * Constructor for the messages with a language string.<p> 097 * 098 * The <code>language</code> is a 2 letter language ISO code, e.g. <code>"EN"</code>.<p> 099 * 100 * The Locale for the messages will be created like this:<br> 101 * <code>new Locale(language, "", "")</code>.<p> 102 * 103 * @param bundleName the base ResourceBundle name 104 * @param language ISO language indentificator for the m_locale of the bundle 105 */ 106 public CmsMessages(String bundleName, String language) { 107 108 this(bundleName, language, "", ""); 109 } 110 111 /** 112 * Constructor for the messages with language and country code strings.<p> 113 * 114 * The <code>language</code> is a 2 letter language ISO code, e.g. <code>"EN"</code>. 115 * The <code>country</code> is a 2 letter country ISO code, e.g. <code>"us"</code>.<p> 116 * 117 * The Locale for the messages will be created like this:<br> 118 * <code>new Locale(language, country, "")</code>. 119 * 120 * @param bundleName the base ResourceBundle name 121 * @param language ISO language indentificator for the m_locale of the bundle 122 * @param country ISO 2 letter country code for the m_locale of the bundle 123 */ 124 public CmsMessages(String bundleName, String language, String country) { 125 126 this(bundleName, language, country, ""); 127 } 128 129 /** 130 * Constructor for the messages with language, country code and variant strings.<p> 131 * 132 * The <code>language</code> is a 2 letter language ISO code, e.g. <code>"EN"</code>. 133 * The <code>country</code> is a 2 letter country ISO code, e.g. <code>"us"</code>. 134 * The <code>variant</code> is a vendor or browser-specific code, e.g. <code>"POSIX"</code>.<p> 135 * 136 * The Locale for the messages will be created like this:<br> 137 * <code>new Locale(language, country, variant)</code>. 138 * 139 * @param bundleName the base ResourceBundle name 140 * @param language language indentificator for the m_locale of the bundle 141 * @param country 2 letter country code for the m_locale of the bundle 142 * @param variant a vendor or browser-specific variant code 143 */ 144 public CmsMessages(String bundleName, String language, String country, String variant) { 145 146 this(bundleName, new Locale(language, country, variant)); 147 } 148 149 /** 150 * Empty constructor for subclassing.<p> 151 */ 152 protected CmsMessages() { 153 154 // empty constructor for subclassing 155 } 156 157 /** 158 * Formats an unknown key.<p> 159 * 160 * @param keyName the key to format 161 * @return the formatted unknown key 162 * 163 * @see #isUnknownKey(String) 164 */ 165 public static String formatUnknownKey(String keyName) { 166 167 StringBuffer buf = new StringBuffer(64); 168 buf.append(UNKNOWN_KEY_EXTENSION); 169 buf.append(" "); 170 buf.append(keyName); 171 buf.append(" "); 172 buf.append(UNKNOWN_KEY_EXTENSION); 173 return buf.toString(); 174 } 175 176 /** 177 * Returns <code>true</code> if the provided value matches the scheme 178 * <code>"??? " + keyName + " ???"</code>, that is the value appears to be an unknown key.<p> 179 * 180 * Also returns <code>true</code> if the given value is <code>null</code>.<p> 181 * 182 * @param value the value to check 183 * @return true if the value is matches the scheme for unknown keys 184 * 185 * @see #formatUnknownKey(String) 186 */ 187 public static boolean isUnknownKey(String value) { 188 189 return (value == null) || (value.startsWith(UNKNOWN_KEY_EXTENSION)); 190 } 191 192 /** 193 * @see java.lang.Object#equals(java.lang.Object) 194 */ 195 @Override 196 public boolean equals(Object obj) { 197 198 if (obj == this) { 199 return true; 200 } 201 if (obj instanceof CmsMultiMessages) { 202 return false; 203 } 204 if (obj instanceof CmsMessages) { 205 CmsMessages other = (CmsMessages)obj; 206 return other.getBundleName().equals(m_bundleName) && other.getLocale().equals(m_locale); 207 } 208 return false; 209 } 210 211 /** 212 * Returns a formated date String from a Date value, 213 * the format being {@link DateFormat#SHORT} and the locale 214 * based on this instance.<p> 215 * 216 * @param date the Date object to format as String 217 * @return the formatted date 218 */ 219 public String getDate(Date date) { 220 221 return CmsDateUtil.getDate(date, DateFormat.SHORT, m_locale); 222 } 223 224 /** 225 * Returns a formated date String from a Date value, 226 * the formatting based on the provided option and the locale 227 * based on this instance.<p> 228 * 229 * @param date the Date object to format as String 230 * @param format the format to use, see {@link CmsMessages} for possible values 231 * @return the formatted date 232 */ 233 public String getDate(Date date, int format) { 234 235 return CmsDateUtil.getDate(date, format, m_locale); 236 } 237 238 /** 239 * Returns a formated date String from a timestamp value, 240 * the format being {@link DateFormat#SHORT} and the locale 241 * based on this instance.<p> 242 * 243 * @param time the time value to format as date 244 * @return the formatted date 245 */ 246 public String getDate(long time) { 247 248 return CmsDateUtil.getDate(new Date(time), DateFormat.SHORT, m_locale); 249 } 250 251 /** 252 * Returns a formated date and time String from a Date value, 253 * the format being {@link DateFormat#SHORT} and the locale 254 * based on this instance.<p> 255 * 256 * @param date the Date object to format as String 257 * @return the formatted date and time 258 */ 259 public String getDateTime(Date date) { 260 261 return CmsDateUtil.getDateTime(date, DateFormat.SHORT, m_locale); 262 } 263 264 /** 265 * Returns a formated date and time String from a Date value, 266 * the formatting based on the provided option and the locale 267 * based on this instance.<p> 268 * 269 * @param date the Date object to format as String 270 * @param format the format to use, see {@link CmsMessages} for possible values 271 * @return the formatted date and time 272 */ 273 public String getDateTime(Date date, int format) { 274 275 return CmsDateUtil.getDateTime(date, format, m_locale); 276 } 277 278 /** 279 * Returns a formated date and time String from a timestamp value, 280 * the format being {@link DateFormat#SHORT} and the locale 281 * based on this instance.<p> 282 * 283 * @param time the time value to format as date 284 * @return the formatted date and time 285 */ 286 public String getDateTime(long time) { 287 288 return CmsDateUtil.getDateTime(new Date(time), DateFormat.SHORT, m_locale); 289 } 290 291 /** 292 * Returns the locale to use for looking up this messages.<p> 293 * 294 * @return the locale to use for looking up this messages 295 */ 296 public Locale getLocale() { 297 298 return m_locale; 299 } 300 301 /** 302 * Returns the resource bundle this message object was initialized with.<p> 303 * 304 * @return the resource bundle this message object was initialized with or null if initialization was not successful 305 */ 306 public ResourceBundle getResourceBundle() { 307 308 return m_resourceBundle; 309 } 310 311 /** 312 * Directly calls the getString(String) method of the wrapped ResourceBundle.<p> 313 * 314 * If you use this this class on a template, you should consider using 315 * the {@link #key(String)} method to get the value from the ResourceBundle because it 316 * handles the exception for you in a convenient way. 317 * 318 * @param keyName the key 319 * @return the resource string for the given key 320 * 321 * @throws CmsMessageException in case the key is not found or the bundle is not initialized 322 */ 323 public String getString(String keyName) throws CmsMessageException { 324 325 if (m_resourceBundle != null) { 326 try { 327 return m_resourceBundle.getString(keyName); 328 } catch (MissingResourceException e) { 329 throw new CmsMessageException( 330 Messages.get().container(Messages.ERR_CANT_FIND_RESOURCE_FOR_BUNDLE_2, keyName, m_bundleName)); 331 } 332 } else { 333 throw new CmsMessageException( 334 Messages.get().container(Messages.ERR_MESSAGE_BUNDLE_NOT_INITIALIZED_1, m_bundleName)); 335 } 336 } 337 338 /** 339 * @see java.lang.Object#hashCode() 340 */ 341 @Override 342 public int hashCode() { 343 344 return m_locale.hashCode() + (m_bundleName == null ? 0 : m_bundleName.hashCode()); 345 } 346 347 /** 348 * Checks if the bundle was properly initialized. 349 * 350 * @return <code>true</code> if bundle was initialized, <code>false</code> otherwise 351 */ 352 public boolean isInitialized() { 353 354 return (m_resourceBundle != null); 355 } 356 357 /** 358 * Indicates that users of this CmsMessages instance should not cache message from it.<p> 359 * 360 * @return true if messages from this CmsMessages instance should not be cached 361 */ 362 public boolean isUncacheable() { 363 364 return (m_resourceBundle instanceof CmsVfsResourceBundle); 365 } 366 367 /** 368 * Returns the localized resource string for a given message key.<p> 369 * 370 * If the key was not found in the bundle, the return value is 371 * <code>"??? " + keyName + " ???"</code>. This will also be returned 372 * if the bundle was not properly initialized first. 373 * 374 * @param keyName the key for the desired string 375 * @return the resource string for the given key 376 */ 377 public String key(String keyName) { 378 379 return key(keyName, false); 380 } 381 382 /** 383 * Returns the localized resource string for a given message key.<p> 384 * 385 * If the key was not found in the bundle, the return value 386 * depends on the setting of the allowNull parameter. If set to false, 387 * the return value is always a String in the format 388 * <code>"??? " + keyName + " ???"</code>. 389 * If set to true, null is returned if the key is not found. 390 * This will also be returned 391 * if the bundle was not properly initialized first. 392 * 393 * @param keyName the key for the desired string 394 * @param allowNull if true, 'null' is an allowed return value 395 * @return the resource string for the given key 396 */ 397 public String key(String keyName, boolean allowNull) { 398 399 try { 400 if (m_resourceBundle != null) { 401 return m_resourceBundle.getString(keyName); 402 } 403 } catch (MissingResourceException e) { 404 // not found, return warning 405 if (allowNull) { 406 return null; 407 } 408 } 409 return formatUnknownKey(keyName); 410 } 411 412 /** 413 * Returns the selected localized message for the initialized resource bundle and locale.<p> 414 * 415 * Convenience method for messages with one argument.<p> 416 * 417 * @param key the message key 418 * @param arg0 the message argument 419 * 420 * @return the selected localized message for the initialized resource bundle and locale 421 */ 422 public String key(String key, Object arg0) { 423 424 return key(key, new Object[] {arg0}); 425 } 426 427 /** 428 * Returns the selected localized message for the initialized resource bundle and locale.<p> 429 * 430 * Convenience method for messages with two arguments.<p> 431 * 432 * @param key the message key 433 * @param arg0 the first message argument 434 * @param arg1 the second message argument 435 * 436 * @return the selected localized message for the initialized resource bundle and locale 437 */ 438 public String key(String key, Object arg0, Object arg1) { 439 440 return key(key, new Object[] {arg0, arg1}); 441 } 442 443 /** 444 * Returns the selected localized message for the initialized resource bundle and locale.<p> 445 * 446 * Convenience method for messages with three arguments.<p> 447 * 448 * @param key the message key 449 * @param arg0 the first message argument 450 * @param arg1 the second message argument 451 * @param arg2 the third message argument 452 * 453 * @return the selected localized message for the initialized resource bundle and locale 454 */ 455 public String key(String key, Object arg0, Object arg1, Object arg2) { 456 457 return key(key, new Object[] {arg0, arg1, arg2}); 458 } 459 460 /** 461 * Returns the selected localized message for the initialized resource bundle and locale.<p> 462 * 463 * If the key was found in the bundle, it will be formatted using 464 * a <code>{@link MessageFormat}</code> using the provided parameters.<p> 465 * 466 * If the key was not found in the bundle, the return value is 467 * <code>"??? " + keyName + " ???"</code>. This will also be returned 468 * if the bundle was not properly initialized first. 469 * 470 * @param key the message key 471 * @param args the message arguments 472 * 473 * @return the selected localized message for the initialized resource bundle and locale 474 */ 475 public String key(String key, Object[] args) { 476 477 return key(key, args, CmsMessages::formatUnknownKey); 478 } 479 480 /** 481 * Returns the selected localized message for the initialized resource bundle and locale.<p> 482 * 483 * If the key was found in the bundle, it will be formatted using 484 * a <code>{@link MessageFormat}</code> using the provided parameters.<p> 485 * 486 * If the key was not found in the bundle, the return value will be the result of passing the key to 487 * the unknownKeyFormatter function. 488 * 489 * @param key the message key 490 * @param args the message arguments 491 * @param unknownKeyFormatter the function for formatting unknown keys 492 * 493 * @return the selected localized message for the initialized resource bundle and locale 494 */ 495 public String key(String key, Object[] args, Function<String, String> unknownKeyFormatter) { 496 497 String result = key(key, true); 498 if (result == null) { 499 // key was not found 500 result = unknownKeyFormatter.apply(key); 501 } else { 502 if ((args == null) || (args.length == 0)) { 503 // A message string actually only needs to be a valid message format if it actually is used with parameters. 504 // In practice there are several message strings that contain text in curly braces and are thus not valid message formats. 505 // So we just return the message string as-is. 506 return result; 507 } 508 try { 509 // key was found in the bundle - create and apply the formatter 510 MessageFormat formatter = new MessageFormat(result, m_locale); 511 result = formatter.format(args); 512 } catch (Exception e) { 513 // illegal message format - don't propagate the exception, just return the message string, with unfilled placeholders 514 // (this is probably better than crashing) 515 LOG.error(e.getLocalizedMessage(), e); 516 } 517 } 518 // return the result 519 return result; 520 } 521 522 /** 523 * Returns the localized resource string for a given message key.<p> 524 * 525 * If the key was not found in the bundle, the provided default value 526 * is returned.<p> 527 * 528 * @param keyName the key for the desired string 529 * @param defaultValue the default value in case the key does not exist in the bundle 530 * @return the resource string for the given key it it exists, or the given default if not 531 */ 532 public String keyDefault(String keyName, String defaultValue) { 533 534 String result = key(keyName, true); 535 return (result == null) ? defaultValue : result; 536 } 537 538 /** 539 * Returns the localized resource string for a given message key, 540 * treating all values appended with "|" as replacement parameters.<p> 541 * 542 * If the key was found in the bundle, it will be formatted using 543 * a <code>{@link MessageFormat}</code> using the provided parameters. 544 * The parameters have to be appended to the key separated by a "|". 545 * For example, the keyName <code>error.message|First|Second</code> 546 * would use the key <code>error.message</code> with the parameters 547 * <code>First</code> and <code>Second</code>. This would be the same as calling 548 * <code>{@link CmsMessages#key(String, Object[])}</code>.<p> 549 * 550 * If no parameters are appended with "|", this is the same as calling 551 * <code>{@link CmsMessages#key(String)}</code>.<p> 552 * 553 * If the key was not found in the bundle, the return value is 554 * <code>"??? " + keyName + " ???"</code>. This will also be returned 555 * if the bundle was not properly initialized first. 556 * 557 * @param keyName the key for the desired string, optinally containing parameters appended with a "|" 558 * @return the resource string for the given key 559 * 560 * @see #key(String, Object[]) 561 * @see #key(String) 562 */ 563 public String keyWithParams(String keyName) { 564 565 return keyWithParams(keyName, CmsMessages::formatUnknownKey); 566 } 567 568 /** 569 * Returns the localized resource string for a given message key, 570 * treating all values appended with "|" as replacement parameters.<p> 571 * 572 * If the key was found in the bundle, it will be formatted using 573 * a <code>{@link MessageFormat}</code> using the provided parameters. 574 * The parameters have to be appended to the key separated by a "|". 575 * For example, the keyName <code>error.message|First|Second</code> 576 * would use the key <code>error.message</code> with the parameters 577 * <code>First</code> and <code>Second</code>. This would be the same as calling 578 * <code>{@link CmsMessages#key(String, Object[])}</code>.<p> 579 * 580 * If no parameters are appended with "|", this is the same as calling 581 * <code>{@link CmsMessages#key(String)}</code>.<p> 582 * 583 * If the key was not found in the bundle, the function from the unknownKeyFormatter parameter is used to generate the return value. 584 * 585 * @param keyName the key for the desired string, optinally containing parameters appended with a "|" 586 * @param unknownKeyFormatter the function to use to generate the returned result for unknown keys 587 * @return the resource string for the given key 588 * 589 * @see #key(String, Object[]) 590 * @see #key(String) 591 */ 592 public String keyWithParams(String keyName, Function<String, String> unknownKeyFormatter) { 593 594 if (keyName.indexOf('|') == -1) { 595 // no separator found, key has no parameters 596 return key(keyName, null, unknownKeyFormatter); 597 } else { 598 // this key contains parameters 599 String[] values = CmsStringUtil.splitAsArray(keyName, '|'); 600 String cutKeyName = values[0]; 601 String[] params = new String[values.length - 1]; 602 System.arraycopy(values, 1, params, 0, params.length); 603 return key(cutKeyName, params, unknownKeyFormatter); 604 } 605 } 606 607 /** 608 * @see java.lang.Object#toString() 609 */ 610 @Override 611 public String toString() { 612 613 StringBuffer result = new StringBuffer(); 614 615 result.append('['); 616 result.append(this.getClass().getName()); 617 result.append(", baseName: "); 618 result.append(m_bundleName); 619 result.append(", locale: "); 620 result.append(getLocale()); 621 result.append(']'); 622 623 return result.toString(); 624 } 625 626 /** 627 * Returns the name of the resource bundle this object was initialized with.<p> 628 * 629 * @return the name of the resource bundle this object was initialized with 630 */ 631 protected String getBundleName() { 632 633 return m_bundleName; 634 } 635 636 /** 637 * Sets the bundleName.<p> 638 * 639 * @param bundleName the bundleName to set 640 */ 641 protected void setBundleName(String bundleName) { 642 643 m_bundleName = bundleName; 644 } 645 646 /** 647 * Sets the locale.<p> 648 * 649 * @param locale the locale to set 650 */ 651 protected void setLocale(Locale locale) { 652 653 m_locale = locale; 654 } 655 656 /** 657 * Sets the resource bundle.<p> 658 * 659 * @param resourceBundle the resource bundle to set 660 */ 661 protected void setResourceBundle(ResourceBundle resourceBundle) { 662 663 m_resourceBundle = resourceBundle; 664 } 665}