KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > context > support > ResourceBundleMessageSource


1 /*
2  * Copyright 2002-2007 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
17 package org.springframework.context.support;
18
19 import java.text.MessageFormat JavaDoc;
20 import java.util.HashMap JavaDoc;
21 import java.util.Locale JavaDoc;
22 import java.util.Map JavaDoc;
23 import java.util.MissingResourceException JavaDoc;
24 import java.util.ResourceBundle JavaDoc;
25
26 import org.springframework.beans.factory.BeanClassLoaderAware;
27 import org.springframework.util.Assert;
28 import org.springframework.util.ClassUtils;
29 import org.springframework.util.StringUtils;
30
31 /**
32  * {@link org.springframework.context.MessageSource} implementation that
33  * accesses resource bundles using specified basenames. This class relies
34  * on the underlying JDK's {@link java.util.ResourceBundle} implementation,
35  * in combination with the JDK's standard message parsing provided by
36  * {@link java.text.MessageFormat}.
37  *
38  * <p>This MessageSource caches both the accessed ResourceBundle instances and
39  * the generated MessageFormats for each message. It also implements rendering of
40  * no-arg messages without MessageFormat, as supported by the AbstractMessageSource
41  * base class. The caching provided by this MessageSource is significantly faster
42  * than the built-in caching of the <code>java.util.ResourceBundle</code> class.
43  *
44  * <p>Unfortunately, <code>java.util.ResourceBundle</code> caches loaded bundles
45  * forever: Reloading a bundle during VM execution is <i>not</i> possible.
46  * As this MessageSource relies on ResourceBundle, it faces the same limitation.
47  * Consider {@link ReloadableResourceBundleMessageSource} for an alternative
48  * that is capable of refreshing the underlying bundle files.
49  *
50  * @author Rod Johnson
51  * @author Juergen Hoeller
52  * @see #setBasenames
53  * @see ReloadableResourceBundleMessageSource
54  * @see java.util.ResourceBundle
55  * @see java.text.MessageFormat
56  */

57 public class ResourceBundleMessageSource extends AbstractMessageSource implements BeanClassLoaderAware {
58
59     private String JavaDoc[] basenames = new String JavaDoc[0];
60
61     private ClassLoader JavaDoc bundleClassLoader;
62
63     private ClassLoader JavaDoc beanClassLoader = ClassUtils.getDefaultClassLoader();
64
65     /**
66      * Cache to hold loaded ResourceBundles.
67      * This Map is keyed with the bundle basename, which holds a Map that is
68      * keyed with the Locale and in turn holds the ResourceBundle instances.
69      * This allows for very efficient hash lookups, significantly faster
70      * than the ResourceBundle class's own cache.
71      */

72     private final Map JavaDoc cachedResourceBundles = new HashMap JavaDoc();
73
74     /**
75      * Cache to hold already generated MessageFormats.
76      * This Map is keyed with the ResourceBundle, which holds a Map that is
77      * keyed with the message code, which in turn holds a Map that is keyed
78      * with the Locale and holds the MessageFormat values. This allows for
79      * very efficient hash lookups without concatenated keys.
80      * @see #getMessageFormat
81      */

82     private final Map JavaDoc cachedBundleMessageFormats = new HashMap JavaDoc();
83
84
85     /**
86      * Set a single basename, following {@link java.util.ResourceBundle} conventions:
87      * essentially, a fully-qualified classpath location. If it doesn't contain a
88      * package qualifier (such as <code>org.mypackage</code>), it will be resolved
89      * from the classpath root.
90      * <p>Messages will normally be held in the "/lib" or "/classes" directory of
91      * a web application's WAR structure. They can also be held in jar files on
92      * the class path.
93      * @param basename the single basename
94      * @see #setBasenames
95      * @see java.util.ResourceBundle
96      */

97     public void setBasename(String JavaDoc basename) {
98         setBasenames(new String JavaDoc[] {basename});
99     }
100
101     /**
102      * Set an array of basenames, each following {@link java.util.ResourceBundle}
103      * conventions: essentially, a fully-qualified classpath location. If it
104      * doesn't contain a package qualifier (such as <code>org.mypackage</code>),
105      * it will be resolved from the classpath root.
106      * <p>The associated resource bundles will be checked sequentially
107      * when resolving a message code. Note that message definitions in a
108      * <i>previous</i> resource bundle will override ones in a later bundle,
109      * due to the sequential lookup.
110      * @param basenames an array of basenames
111      * @see #setBasename
112      * @see java.util.ResourceBundle
113      */

114     public void setBasenames(String JavaDoc[] basenames) {
115         if (basenames != null) {
116             this.basenames = new String JavaDoc[basenames.length];
117             for (int i = 0; i < basenames.length; i++) {
118                 String JavaDoc basename = basenames[i];
119                 Assert.hasText(basename, "Basename must not be empty");
120                 this.basenames[i] = basename.trim();
121             }
122         }
123         else {
124             this.basenames = new String JavaDoc[0];
125         }
126     }
127
128     /**
129      * Set the ClassLoader to load resource bundles with.
130      * <p>Default is the containing BeanFactory's
131      * {@link org.springframework.beans.factory.BeanClassLoaderAware bean ClassLoader},
132      * or the default ClassLoader determined by
133      * {@link org.springframework.util.ClassUtils#getDefaultClassLoader()}
134      * if not running within a BeanFactory.
135      */

136     public void setBundleClassLoader(ClassLoader JavaDoc classLoader) {
137         this.bundleClassLoader = classLoader;
138     }
139
140     /**
141      * Return the ClassLoader to load resource bundles with.
142      * <p>Default is the containing BeanFactory's bean ClassLoader.
143      * @see #setBundleClassLoader
144      */

145     protected ClassLoader JavaDoc getBundleClassLoader() {
146         return (this.bundleClassLoader != null ? this.bundleClassLoader : this.beanClassLoader);
147     }
148
149     public void setBeanClassLoader(ClassLoader JavaDoc classLoader) {
150         this.beanClassLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
151     }
152
153
154     /**
155      * Resolves the given message code as key in the registered resource bundles,
156      * returning the value found in the bundle as-is (without MessageFormat parsing).
157      */

158     protected String JavaDoc resolveCodeWithoutArguments(String JavaDoc code, Locale JavaDoc locale) {
159         String JavaDoc result = null;
160         for (int i = 0; result == null && i < this.basenames.length; i++) {
161             ResourceBundle JavaDoc bundle = getResourceBundle(this.basenames[i], locale);
162             if (bundle != null) {
163                 result = getStringOrNull(bundle, code);
164             }
165         }
166         return result;
167     }
168
169     /**
170      * Resolves the given message code as key in the registered resource bundles,
171      * using a cached MessageFormat instance per message code.
172      */

173     protected MessageFormat JavaDoc resolveCode(String JavaDoc code, Locale JavaDoc locale) {
174         MessageFormat JavaDoc messageFormat = null;
175         for (int i = 0; messageFormat == null && i < this.basenames.length; i++) {
176             ResourceBundle JavaDoc bundle = getResourceBundle(this.basenames[i], locale);
177             if (bundle != null) {
178                 messageFormat = getMessageFormat(bundle, code, locale);
179             }
180         }
181         return messageFormat;
182     }
183
184
185     /**
186      * Return a ResourceBundle for the given basename and code,
187      * fetching already generated MessageFormats from the cache.
188      * @param basename the basename of the ResourceBundle
189      * @param locale the Locale to find the ResourceBundle for
190      * @return the resulting ResourceBundle, or <code>null</code> if none
191      * found for the given basename and Locale
192      */

193     protected ResourceBundle JavaDoc getResourceBundle(String JavaDoc basename, Locale JavaDoc locale) {
194         synchronized (this.cachedResourceBundles) {
195             Map JavaDoc localeMap = (Map JavaDoc) this.cachedResourceBundles.get(basename);
196             if (localeMap != null) {
197                 ResourceBundle JavaDoc bundle = (ResourceBundle JavaDoc) localeMap.get(locale);
198                 if (bundle != null) {
199                     return bundle;
200                 }
201             }
202             try {
203                 ResourceBundle JavaDoc bundle = doGetBundle(basename, locale);
204                 if (localeMap == null) {
205                     localeMap = new HashMap JavaDoc();
206                     this.cachedResourceBundles.put(basename, localeMap);
207                 }
208                 localeMap.put(locale, bundle);
209                 return bundle;
210             }
211             catch (MissingResourceException JavaDoc ex) {
212                 if (logger.isWarnEnabled()) {
213                     logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
214                 }
215                 // Assume bundle not found
216
// -> do NOT throw the exception to allow for checking parent message source.
217
return null;
218             }
219         }
220     }
221
222     /**
223      * Obtain the resource bundle for the given basename and Locale.
224      * @param basename the basename to look for
225      * @param locale the Locale to look for
226      * @return the corresponding ResourceBundle
227      * @throws MissingResourceException if no matching bundle could be found
228      * @see java.util.ResourceBundle#getBundle(String, java.util.Locale, ClassLoader)
229      * @see #getBundleClassLoader()
230      */

231     protected ResourceBundle JavaDoc doGetBundle(String JavaDoc basename, Locale JavaDoc locale) throws MissingResourceException JavaDoc {
232         return ResourceBundle.getBundle(basename, locale, getBundleClassLoader());
233     }
234
235     /**
236      * Return a MessageFormat for the given bundle and code,
237      * fetching already generated MessageFormats from the cache.
238      * @param bundle the ResourceBundle to work on
239      * @param code the message code to retrieve
240      * @param locale the Locale to use to build the MessageFormat
241      * @return the resulting MessageFormat, or <code>null</code> if no message
242      * defined for the given code
243      * @throws MissingResourceException if thrown by the ResourceBundle
244      */

245     protected MessageFormat JavaDoc getMessageFormat(ResourceBundle JavaDoc bundle, String JavaDoc code, Locale JavaDoc locale)
246             throws MissingResourceException JavaDoc {
247
248         synchronized (this.cachedBundleMessageFormats) {
249             Map JavaDoc codeMap = (Map JavaDoc) this.cachedBundleMessageFormats.get(bundle);
250             Map JavaDoc localeMap = null;
251             if (codeMap != null) {
252                 localeMap = (Map JavaDoc) codeMap.get(code);
253                 if (localeMap != null) {
254                     MessageFormat JavaDoc result = (MessageFormat JavaDoc) localeMap.get(locale);
255                     if (result != null) {
256                         return result;
257                     }
258                 }
259             }
260
261             String JavaDoc msg = getStringOrNull(bundle, code);
262             if (msg != null) {
263                 if (codeMap == null) {
264                     codeMap = new HashMap JavaDoc();
265                     this.cachedBundleMessageFormats.put(bundle, codeMap);
266                 }
267                 if (localeMap == null) {
268                     localeMap = new HashMap JavaDoc();
269                     codeMap.put(code, localeMap);
270                 }
271                 MessageFormat JavaDoc result = createMessageFormat(msg, locale);
272                 localeMap.put(locale, result);
273                 return result;
274             }
275
276             return null;
277         }
278     }
279
280     private String JavaDoc getStringOrNull(ResourceBundle JavaDoc bundle, String JavaDoc key) {
281         try {
282             return bundle.getString(key);
283         }
284         catch (MissingResourceException JavaDoc ex) {
285             // Assume key not found
286
// -> do NOT throw the exception to allow for checking parent message source.
287
return null;
288         }
289     }
290
291
292     /**
293      * Show the configuration of this MessageSource.
294      */

295     public String JavaDoc toString() {
296         return getClass().getName() + ": basenames=[" +
297                 StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
298     }
299
300 }
301
Popular Tags