1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.context.support;
  17. import java.text.MessageFormat;
  18. import java.util.HashMap;
  19. import java.util.Locale;
  20. import java.util.Map;
  21. import java.util.MissingResourceException;
  22. import java.util.ResourceBundle;
  23. import org.springframework.util.StringUtils;
  24. /**
  25. * MessageSource that accesses the ResourceBundles with the specified basenames.
  26. * This class relies on the underlying JDK's java.util.ResourceBundle implementation.
  27. *
  28. * <p>Unfortunately, java.util.ResourceBundle caches loaded bundles indefinitely.
  29. * Reloading a bundle during VM execution is <i>not</i> possible by any means.
  30. * As this MessageSource relies on ResourceBundle, it faces the same limitation.
  31. * Consider ReloadableResourceBundleMessageSource for an alternative.
  32. *
  33. * @author Rod Johnson
  34. * @author Juergen Hoeller
  35. * @see #setBasenames
  36. * @see ReloadableResourceBundleMessageSource
  37. * @see java.util.ResourceBundle
  38. */
  39. public class ResourceBundleMessageSource extends AbstractMessageSource {
  40. private String[] basenames;
  41. /**
  42. * Cache to hold already generated MessageFormats per message code.
  43. * Note that this Map contains the actual code Map, keyed with the Locale.
  44. * @see #getMessageFormat
  45. */
  46. private final Map cachedMessageFormats = new HashMap();
  47. /**
  48. * Set a single basename, following ResourceBundle conventions:
  49. * It is a fully-qualified classname. If it doesn't contain a package qualifier
  50. * (such as org.mypackage), it will be resolved from the classpath root.
  51. * <p>Messages will normally be held in the /lib or /classes directory of a WAR.
  52. * They can also be held in Jars on the class path. For example, a Jar in an
  53. * application's manifest classpath could contain messages for the application.
  54. * @param basename the single basename
  55. * @see #setBasenames
  56. * @see java.util.ResourceBundle
  57. */
  58. public void setBasename(String basename) {
  59. setBasenames(new String[] {basename});
  60. }
  61. /**
  62. * Set an array of basenames, each following ResourceBundle conventions.
  63. * The associated resource bundles will be checked sequentially when
  64. * resolving a message code.
  65. * <p>Note that message definitions in a <i>previous</i> resource bundle
  66. * will override ones in a later bundle, due to the sequential lookup.
  67. * @param basenames an array of basenames
  68. * @see #setBasename
  69. * @see java.util.ResourceBundle
  70. */
  71. public void setBasenames(String[] basenames) {
  72. this.basenames = basenames;
  73. }
  74. protected final MessageFormat resolveCode(String code, Locale locale) {
  75. MessageFormat messageFormat = null;
  76. for (int i = 0; messageFormat == null && i < this.basenames.length; i++) {
  77. messageFormat = resolve(this.basenames[i], code, locale);
  78. }
  79. return messageFormat;
  80. }
  81. /**
  82. * Return a MessageFormat for the given bundle basename, message code,
  83. * and Locale.
  84. * @param basename the basename of the bundle
  85. * @param code the message code to retrieve
  86. * @param locale the Locale to resolve for
  87. * @return the resulting MessageFormat
  88. */
  89. protected MessageFormat resolve(String basename, String code, Locale locale) {
  90. try {
  91. ResourceBundle bundle = ResourceBundle.getBundle(basename, locale,
  92. Thread.currentThread().getContextClassLoader());
  93. try {
  94. return getMessageFormat(bundle, code, locale);
  95. }
  96. catch (MissingResourceException ex) {
  97. // assume key not found
  98. // -> do NOT throw the exception to allow for checking parent message source
  99. return null;
  100. }
  101. }
  102. catch (MissingResourceException ex) {
  103. logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
  104. // assume bundle not found
  105. // -> do NOT throw the exception to allow for checking parent message source
  106. return null;
  107. }
  108. }
  109. /**
  110. * Return a MessageFormat for the given bundle and code,
  111. * fetching already generated MessageFormats from the cache.
  112. * @param bundle the ResourceBundle to work on
  113. * @param code the message code to retrieve
  114. * @param locale the Locale to use to build the MessageFormat
  115. * @return the resulting MessageFormat
  116. */
  117. protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
  118. throws MissingResourceException {
  119. synchronized (this.cachedMessageFormats) {
  120. Map codeMap = (Map) this.cachedMessageFormats.get(bundle);
  121. Map localeMap = null;
  122. if (codeMap != null) {
  123. localeMap = (Map) codeMap.get(code);
  124. if (localeMap != null) {
  125. MessageFormat result = (MessageFormat) localeMap.get(locale);
  126. if (result != null) {
  127. return result;
  128. }
  129. }
  130. }
  131. String msg = bundle.getString(code);
  132. if (msg != null) {
  133. if (codeMap == null) {
  134. codeMap = new HashMap();
  135. this.cachedMessageFormats.put(bundle, codeMap);
  136. }
  137. if (localeMap == null) {
  138. localeMap = new HashMap();
  139. codeMap.put(code, localeMap);
  140. }
  141. MessageFormat result = createMessageFormat(msg, locale);
  142. localeMap.put(locale, result);
  143. return result;
  144. }
  145. return null;
  146. }
  147. }
  148. /**
  149. * Show the configuration of this MessageSource.
  150. */
  151. public String toString() {
  152. return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
  153. }
  154. }