From f108deb1149746efc54da480e43c9a88e1cd3e79 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 4 Feb 2016 19:57:40 +0100 Subject: [PATCH] (Reloadable)ResourceBundleMessageSource allows for adding resources This turned into the extraction of a common AbstractResourceBasedMessageSource base class which not only features addBasenames but also getBasenameSet and setCacheMillis. Issue: SPR-10314 --- .../AbstractResourceBasedMessageSource.java | 208 ++++++++++++++++++ ...ReloadableResourceBundleMessageSource.java | 156 ++----------- .../support/ResourceBundleMessageSource.java | 161 +++----------- 3 files changed, 265 insertions(+), 260 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java new file mode 100644 index 00000000000..a2eddf9fca4 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractResourceBasedMessageSource.java @@ -0,0 +1,208 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.support; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Abstract base class for {@code MessageSource} implementations based on + * resource bundle conventions, such as {@link ResourceBundleMessageSource} + * and {@link ReloadableResourceBundleMessageSource}. Provides common + * configuration methods and corresponding semantic definitions. + * + * @author Juergen Hoeller + * @since 4.3 + * @see ResourceBundleMessageSource + * @see ReloadableResourceBundleMessageSource + */ +public abstract class AbstractResourceBasedMessageSource extends AbstractMessageSource { + + private final Set basenameSet = new LinkedHashSet(4); + + private String defaultEncoding; + + private boolean fallbackToSystemLocale = true; + + private long cacheMillis = -1; + + + /** + * Set a single basename, following the basic ResourceBundle convention + * of not specifying file extension or language codes. The resource location + * format is up to the specific {@code MessageSource} implementation. + *

Regular and XMl properties files are supported: .g. "messages" will find + * a "messages.properties", "messages_en.properties" etc arrangement as well + * as "messages.xml", "messages_en.xml" etc. + * @param basename the single basename + * @see #setBasenames + * @see org.springframework.core.io.ResourceEditor + * @see java.util.ResourceBundle + */ + public void setBasename(String basename) { + setBasenames(basename); + } + + /** + * Set an array of basenames, each following the basic ResourceBundle convention + * of not specifying file extension or language codes. The resource location + * format is up to the specific {@code MessageSource} implementation. + *

Regular and XMl properties files are supported: .g. "messages" will find + * a "messages.properties", "messages_en.properties" etc arrangement as well + * as "messages.xml", "messages_en.xml" etc. + *

The associated resource bundles will be checked sequentially when resolving + * a message code. Note that message definitions in a previous resource + * bundle will override ones in a later bundle, due to the sequential lookup. + *

Note: In contrast to {@link #addBasenames}, this replaces existing entries + * with the given names and can therefore also be used to reset the configuration. + * @param basenames an array of basenames + * @see #setBasename + * @see java.util.ResourceBundle + */ + public void setBasenames(String... basenames) { + this.basenameSet.clear(); + addBasenames(basenames); + } + + /** + * Add the specified basenames to the existing basename configuration. + *

Note: If a given basename already exists, the position of its entry + * will remain as in the original set. New entries will be added at the + * end of the list, to be searched after existing basenames. + * @since 4.3 + * @see #setBasenames + * @see java.util.ResourceBundle + */ + public void addBasenames(String... basenames) { + if (!ObjectUtils.isEmpty(basenames)) { + for (String basename : basenames) { + Assert.hasText(basename, "Basename must not be empty"); + this.basenameSet.add(basename.trim()); + } + } + } + + /** + * Return this {@code MessageSource}'s basename set, containing entries + * in the order of registration. + *

Calling code may introspect this set as well as add or remove entries. + * @since 4.3 + * @see #addBasenames + */ + public Set getBasenameSet() { + return this.basenameSet; + } + + /** + * Set the default charset to use for parsing properties files. + * Used if no file-specific charset is specified for a file. + *

Default is none, using the {@code java.util.Properties} + * default encoding: ISO-8859-1. + *

Only applies to classic properties files, not to XML files. + * @param defaultEncoding the default charset + */ + public void setDefaultEncoding(String defaultEncoding) { + this.defaultEncoding = defaultEncoding; + } + + /** + * Return the default charset to use for parsing properties files, if any. + * @since 4.3 + */ + protected String getDefaultEncoding() { + return this.defaultEncoding; + } + + /** + * Set whether to fall back to the system Locale if no files for a specific + * Locale have been found. Default is "true"; if this is turned off, the only + * fallback will be the default file (e.g. "messages.properties" for + * basename "messages"). + *

Falling back to the system Locale is the default behavior of + * {@code java.util.ResourceBundle}. However, this is often not desirable + * in an application server environment, where the system Locale is not relevant + * to the application at all: Set this flag to "false" in such a scenario. + */ + public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { + this.fallbackToSystemLocale = fallbackToSystemLocale; + } + + /** + * Return whether to fall back to the system Locale if no files for a specific + * Locale have been found. + * @since 4.3 + */ + protected boolean isFallbackToSystemLocale() { + return this.fallbackToSystemLocale; + } + + /** + * Set the number of seconds to cache loaded properties files. + *

    + *
  • Default is "-1", indicating to cache forever (just like + * {@code java.util.ResourceBundle}). + *
  • A positive number will cache loaded properties files for the given + * number of seconds. This is essentially the interval between refresh checks. + * Note that a refresh attempt will first check the last-modified timestamp + * of the file before actually reloading it; so if files don't change, this + * interval can be set rather low, as refresh attempts will not actually reload. + *
  • A value of "0" will check the last-modified timestamp of the file on + * every message access. Do not use this in a production environment! + *
+ *

Note that depending on your ClassLoader, expiration might not work reliably + * since the ClassLoader may hold on to a cached version of the bundle file. + * Prefer {@link ReloadableResourceBundleMessageSource} over + * {@link ResourceBundleMessageSource} in such a scenario, in combination with + * a non-classpath location. + */ + public void setCacheSeconds(int cacheSeconds) { + this.cacheMillis = (cacheSeconds * 1000); + } + + /** + * Set the number of milliseconds to cache loaded properties files. + * Note that it is common to set seconds instead: {@link #setCacheSeconds}. + *

    + *
  • Default is "-1", indicating to cache forever (just like + * {@code java.util.ResourceBundle}). + *
  • A positive number will cache loaded properties files for the given + * number of milliseconds. This is essentially the interval between refresh checks. + * Note that a refresh attempt will first check the last-modified timestamp + * of the file before actually reloading it; so if files don't change, this + * interval can be set rather low, as refresh attempts will not actually reload. + *
  • A value of "0" will check the last-modified timestamp of the file on + * every message access. Do not use this in a production environment! + *
+ * @since 4.3 + * @see #setCacheSeconds + */ + public void setCacheMillis(long cacheMillis) { + this.cacheMillis = cacheMillis; + } + + /** + * Return the number of milliseconds to cache loaded properties files. + * @since 4.3 + */ + protected long getCacheMillis() { + return this.cacheMillis; + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java index ab45abf1b08..8f4e1826065 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; -import org.springframework.util.Assert; import org.springframework.util.DefaultPropertiesPersister; import org.springframework.util.PropertiesPersister; import org.springframework.util.StringUtils; @@ -50,29 +49,20 @@ import org.springframework.util.StringUtils; * reloading files based on timestamp changes, but also of loading properties files * with a specific character encoding. It will detect XML property files as well. * - *

In contrast to {@link ResourceBundleMessageSource}, this class supports - * reloading of properties files through the {@link #setCacheSeconds "cacheSeconds"} - * setting, and also through programmatically clearing the properties cache. - * Since application servers typically cache all files loaded from the classpath, - * it is necessary to store resources somewhere else (for example, in the - * "WEB-INF" directory of a web app). Otherwise changes of files in the - * classpath will not be reflected in the application. - * - *

Note that the base names set as {@link #setBasenames "basenames"} property + *

Note that the basenames set as {@link #setBasenames "basenames"} property * are treated in a slightly different fashion than the "basenames" property of * {@link ResourceBundleMessageSource}. It follows the basic ResourceBundle rule of not * specifying file extension or language codes, but can refer to any Spring resource * location (instead of being restricted to classpath resources). With a "classpath:" * prefix, resources can still be loaded from the classpath, but "cacheSeconds" values - * other than "-1" (caching forever) will not work in this case. - * - *

This MessageSource implementation is usually slightly faster than - * {@link ResourceBundleMessageSource}, which builds on {@link java.util.ResourceBundle} - * - in the default mode, i.e. when caching forever. With "cacheSeconds" set to 1, - * message lookup takes about twice as long - with the benefit that changes in - * individual properties files are detected with a maximum delay of 1 second. - * Higher "cacheSeconds" values usually do not make a significant difference. + * other than "-1" (caching forever) might not work reliably in this case. * + *

For a typical web application, message files could be placed into {@code WEB-INF}: + * e.g. a "WEB-INF/messages" basename would fine a "WEB-INF/messages.properties", + * "WEB-INF/messages_en.properties" etc arrangement as well as "WEB-INF/messages.xml", + * "WEB-INF/messages_en.xml" etc. Note that message definitions in a previous + * resource bundle will override ones in a later bundle, due to sequential lookup. + *

This MessageSource can easily be used outside of an * {@link org.springframework.context.ApplicationContext}: It will use a * {@link org.springframework.core.io.DefaultResourceLoader} as default, @@ -94,23 +84,15 @@ import org.springframework.util.StringUtils; * @see ResourceBundleMessageSource * @see java.util.ResourceBundle */ -public class ReloadableResourceBundleMessageSource extends AbstractMessageSource implements ResourceLoaderAware { +public class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements ResourceLoaderAware { private static final String PROPERTIES_SUFFIX = ".properties"; private static final String XML_SUFFIX = ".xml"; - private String[] basenames = new String[0]; - - private String defaultEncoding; - private Properties fileEncodings; - private boolean fallbackToSystemLocale = true; - - private long cacheMillis = -1; - private boolean concurrentRefresh = true; private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister(); @@ -130,66 +112,6 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource new ConcurrentHashMap(); - /** - * Set a single basename, following the basic ResourceBundle convention of - * not specifying file extension or language codes, but in contrast to - * {@link ResourceBundleMessageSource} referring to a Spring resource location: - * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties", - * "WEB-INF/messages_en.properties", etc. - *

XML properties files are also supported: .g. "WEB-INF/messages" will find - * and load "WEB-INF/messages.xml", "WEB-INF/messages_en.xml", etc as well. - * @param basename the single basename - * @see #setBasenames - * @see org.springframework.core.io.ResourceEditor - * @see java.util.ResourceBundle - */ - public void setBasename(String basename) { - setBasenames(basename); - } - - /** - * Set an array of basenames, each following the basic ResourceBundle convention - * of not specifying file extension or language codes, but in contrast to - * {@link ResourceBundleMessageSource} referring to a Spring resource location: - * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties", - * "WEB-INF/messages_en.properties", etc. - *

XML properties files are also supported: .g. "WEB-INF/messages" will find - * and load "WEB-INF/messages.xml", "WEB-INF/messages_en.xml", etc as well. - *

The associated resource bundles will be checked sequentially when resolving - * a message code. Note that message definitions in a previous resource - * bundle will override ones in a later bundle, due to the sequential lookup. - * @param basenames an array of basenames - * @see #setBasename - * @see java.util.ResourceBundle - */ - public void setBasenames(String... basenames) { - if (basenames != null) { - this.basenames = new String[basenames.length]; - for (int i = 0; i < basenames.length; i++) { - String basename = basenames[i]; - Assert.hasText(basename, "Basename must not be empty"); - this.basenames[i] = basename.trim(); - } - } - else { - this.basenames = new String[0]; - } - } - - /** - * Set the default charset to use for parsing properties files. - * Used if no file-specific charset is specified for a file. - *

Default is none, using the {@code java.util.Properties} - * default encoding: ISO-8859-1. - *

Only applies to classic properties files, not to XML files. - * @param defaultEncoding the default charset - * @see #setFileEncodings - * @see org.springframework.util.PropertiesPersister#load - */ - public void setDefaultEncoding(String defaultEncoding) { - this.defaultEncoding = defaultEncoding; - } - /** * Set per-file charsets to use for parsing properties files. *

Only applies to classic properties files, not to XML files. @@ -204,38 +126,6 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource this.fileEncodings = fileEncodings; } - /** - * Set whether to fall back to the system Locale if no files for a specific - * Locale have been found. Default is "true"; if this is turned off, the only - * fallback will be the default file (e.g. "messages.properties" for - * basename "messages"). - *

Falling back to the system Locale is the default behavior of - * {@code java.util.ResourceBundle}. However, this is often not desirable - * in an application server environment, where the system Locale is not relevant - * to the application at all: Set this flag to "false" in such a scenario. - */ - public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { - this.fallbackToSystemLocale = fallbackToSystemLocale; - } - - /** - * Set the number of seconds to cache loaded properties files. - *

    - *
  • Default is "-1", indicating to cache forever (just like - * {@code java.util.ResourceBundle}). - *
  • A positive number will cache loaded properties files for the given - * number of seconds. This is essentially the interval between refresh checks. - * Note that a refresh attempt will first check the last-modified timestamp - * of the file before actually reloading it; so if files don't change, this - * interval can be set rather low, as refresh attempts will not actually reload. - *
  • A value of "0" will check the last-modified timestamp of the file on - * every message access. Do not use this in a production environment! - *
- */ - public void setCacheSeconds(int cacheSeconds) { - this.cacheMillis = (cacheSeconds * 1000); - } - /** * Specify whether to allow for concurrent refresh behavior, i.e. one thread * locked in a refresh attempt for a specific cached properties file whereas @@ -244,6 +134,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource *

Default is "true": This behavior is new as of Spring Framework 4.1, * minimizing contention between threads. If you prefer the old behavior, * i.e. to fully block on refresh, switch this flag to "false". + * @since 4.1 * @see #setCacheSeconds */ public void setConcurrentRefresh(boolean concurrentRefresh) { @@ -281,7 +172,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource */ @Override protected String resolveCodeWithoutArguments(String code, Locale locale) { - if (this.cacheMillis < 0) { + if (getCacheMillis() < 0) { PropertiesHolder propHolder = getMergedProperties(locale); String result = propHolder.getProperty(code); if (result != null) { @@ -289,7 +180,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource } } else { - for (String basename : this.basenames) { + for (String basename : getBasenameSet()) { List filenames = calculateAllFilenames(basename, locale); for (String filename : filenames) { PropertiesHolder propHolder = getProperties(filename); @@ -309,7 +200,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource */ @Override protected MessageFormat resolveCode(String code, Locale locale) { - if (this.cacheMillis < 0) { + if (getCacheMillis() < 0) { PropertiesHolder propHolder = getMergedProperties(locale); MessageFormat result = propHolder.getMessageFormat(code, locale); if (result != null) { @@ -317,7 +208,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource } } else { - for (String basename : this.basenames) { + for (String basename : getBasenameSet()) { List filenames = calculateAllFilenames(basename, locale); for (String filename : filenames) { PropertiesHolder propHolder = getProperties(filename); @@ -347,8 +238,9 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource } Properties mergedProps = newProperties(); mergedHolder = new PropertiesHolder(mergedProps, -1); - for (int i = this.basenames.length - 1; i >= 0; i--) { - List filenames = calculateAllFilenames(this.basenames[i], locale); + String[] basenames = StringUtils.toStringArray(getBasenameSet()); + for (int i = basenames.length - 1; i >= 0; i--) { + List filenames = calculateAllFilenames(basenames[i], locale); for (int j = filenames.size() - 1; j >= 0; j--) { String filename = filenames.get(j); PropertiesHolder propHolder = getProperties(filename); @@ -384,7 +276,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource } List filenames = new ArrayList(7); filenames.addAll(calculateFilenamesForLocale(basename, locale)); - if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) { + if (isFallbackToSystemLocale() && !locale.equals(Locale.getDefault())) { List fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault()); for (String fallbackFilename : fallbackFilenames) { if (!filenames.contains(fallbackFilename)) { @@ -455,7 +347,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource if (propHolder != null) { originalTimestamp = propHolder.getRefreshTimestamp(); - if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - this.cacheMillis) { + if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - getCacheMillis()) { // Up to date return propHolder; } @@ -500,7 +392,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource * @param propHolder the current PropertiesHolder for the bundle */ protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) { - long refreshTimestamp = (this.cacheMillis < 0 ? -1 : System.currentTimeMillis()); + long refreshTimestamp = (getCacheMillis() < 0 ? -1 : System.currentTimeMillis()); Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX); if (!resource.exists()) { @@ -509,7 +401,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource if (resource.exists()) { long fileTimestamp = -1; - if (this.cacheMillis >= 0) { + if (getCacheMillis() >= 0) { // Last-modified timestamp of file will just be read if caching with timeout. try { fileTimestamp = resource.lastModified(); @@ -579,7 +471,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource encoding = this.fileEncodings.getProperty(filename); } if (encoding == null) { - encoding = this.defaultEncoding; + encoding = getDefaultEncoding(); } if (encoding != null) { if (logger.isDebugEnabled()) { @@ -639,7 +531,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractMessageSource @Override public String toString() { - return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]"; + return getClass().getName() + ": basenames=" + getBasenameSet(); } diff --git a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java index 23ae8340aaa..d7dce2b57c9 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/ResourceBundleMessageSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,11 +32,10 @@ import java.util.Map; import java.util.MissingResourceException; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; +import java.util.Set; import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * {@link org.springframework.context.MessageSource} implementation that @@ -51,11 +50,11 @@ import org.springframework.util.StringUtils; * base class. The caching provided by this MessageSource is significantly faster * than the built-in caching of the {@code java.util.ResourceBundle} class. * - *

Unfortunately, {@code java.util.ResourceBundle} caches loaded bundles - * forever: Reloading a bundle during VM execution is not possible. - * As this MessageSource relies on ResourceBundle, it faces the same limitation. - * Consider {@link ReloadableResourceBundleMessageSource} for an alternative - * that is capable of refreshing the underlying bundle files. + *

The basenames follow {@link java.util.ResourceBundle} conventions: essentially, + * a fully-qualified classpath location. If it doesn't contain a package qualifier + * (such as {@code org.mypackage}), it will be resolved from the classpath root. + * Note that the JDK's standard ResourceBundle treats dots as package separators: + * This means that "test.theme" is effectively equivalent to "test/theme". * * @author Rod Johnson * @author Juergen Hoeller @@ -64,15 +63,7 @@ import org.springframework.util.StringUtils; * @see java.util.ResourceBundle * @see java.text.MessageFormat */ -public class ResourceBundleMessageSource extends AbstractMessageSource implements BeanClassLoaderAware { - - private String[] basenames = new String[0]; - - private String defaultEncoding = "ISO-8859-1"; - - private boolean fallbackToSystemLocale = true; - - private long cacheMillis = -1; +public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements BeanClassLoaderAware { private ClassLoader bundleClassLoader; @@ -100,102 +91,6 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement new HashMap>>(); - /** - * Set a single basename, following {@link java.util.ResourceBundle} conventions: - * essentially, a fully-qualified classpath location. If it doesn't contain a - * package qualifier (such as {@code org.mypackage}), it will be resolved - * from the classpath root. - *

Messages will normally be held in the "/lib" or "/classes" directory of - * a web application's WAR structure. They can also be held in jar files on - * the class path. - *

Note that ResourceBundle names are effectively classpath locations: As a - * consequence, the JDK's standard ResourceBundle treats dots as package separators. - * This means that "test.theme" is effectively equivalent to "test/theme", - * just like it is for programmatic {@code java.util.ResourceBundle} usage. - * @see #setBasenames - * @see java.util.ResourceBundle#getBundle(String) - */ - public void setBasename(String basename) { - setBasenames(basename); - } - - /** - * Set an array of basenames, each following {@link java.util.ResourceBundle} - * conventions: essentially, a fully-qualified classpath location. If it - * doesn't contain a package qualifier (such as {@code org.mypackage}), - * it will be resolved from the classpath root. - *

The associated resource bundles will be checked sequentially - * when resolving a message code. Note that message definitions in a - * previous resource bundle will override ones in a later bundle, - * due to the sequential lookup. - *

Note that ResourceBundle names are effectively classpath locations: As a - * consequence, the JDK's standard ResourceBundle treats dots as package separators. - * This means that "test.theme" is effectively equivalent to "test/theme", - * just like it is for programmatic {@code java.util.ResourceBundle} usage. - * @see #setBasename - * @see java.util.ResourceBundle#getBundle(String) - */ - public void setBasenames(String... basenames) { - if (basenames != null) { - this.basenames = new String[basenames.length]; - for (int i = 0; i < basenames.length; i++) { - String basename = basenames[i]; - Assert.hasText(basename, "Basename must not be empty"); - this.basenames[i] = basename.trim(); - } - } - else { - this.basenames = new String[0]; - } - } - - /** - * Set the default charset to use for parsing resource bundle files. - *

Default is the {@code java.util.ResourceBundle} default encoding: - * ISO-8859-1. - * @since 3.1.3 - */ - public void setDefaultEncoding(String defaultEncoding) { - this.defaultEncoding = defaultEncoding; - } - - /** - * Set whether to fall back to the system Locale if no files for a specific - * Locale have been found. Default is "true"; if this is turned off, the only - * fallback will be the default file (e.g. "messages.properties" for - * basename "messages"). - *

Falling back to the system Locale is the default behavior of - * {@code java.util.ResourceBundle}. However, this is often not desirable - * in an application server environment, where the system Locale is not relevant - * to the application at all: Set this flag to "false" in such a scenario. - * @since 3.1.3 - */ - public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { - this.fallbackToSystemLocale = fallbackToSystemLocale; - } - - /** - * Set the number of seconds to cache loaded resource bundle files. - *

    - *
  • Default is "-1", indicating to cache forever. - *
  • A positive number will expire resource bundles after the given - * number of seconds. This is essentially the interval between refresh checks. - * Note that a refresh attempt will first check the last-modified timestamp - * of the file before actually reloading it; so if files don't change, this - * interval can be set rather low, as refresh attempts will not actually reload. - *
  • A value of "0" will check the last-modified timestamp of the file on - * every message access. Do not use this in a production environment! - *
  • Note that depending on your ClassLoader, expiration might not work reliably - * since the ClassLoader may hold on to a cached version of the bundle file. - * Consider {@link ReloadableResourceBundleMessageSource} in combination - * with resource bundle files in a non-classpath location. - *
- * @since 3.1.3 - */ - public void setCacheSeconds(int cacheSeconds) { - this.cacheMillis = (cacheSeconds * 1000); - } - /** * Set the ClassLoader to load resource bundles with. *

Default is the containing BeanFactory's @@ -229,14 +124,17 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement */ @Override protected String resolveCodeWithoutArguments(String code, Locale locale) { - String result = null; - for (int i = 0; result == null && i < this.basenames.length; i++) { - ResourceBundle bundle = getResourceBundle(this.basenames[i], locale); + Set basenames = getBasenameSet(); + for (String basename : basenames) { + ResourceBundle bundle = getResourceBundle(basename, locale); if (bundle != null) { - result = getStringOrNull(bundle, code); + String result = getStringOrNull(bundle, code); + if (result != null) { + return result; + } } } - return result; + return null; } /** @@ -245,14 +143,17 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement */ @Override protected MessageFormat resolveCode(String code, Locale locale) { - MessageFormat messageFormat = null; - for (int i = 0; messageFormat == null && i < this.basenames.length; i++) { - ResourceBundle bundle = getResourceBundle(this.basenames[i], locale); + Set basenames = getBasenameSet(); + for (String basename : basenames) { + ResourceBundle bundle = getResourceBundle(basename, locale); if (bundle != null) { - messageFormat = getMessageFormat(bundle, code, locale); + MessageFormat messageFormat = getMessageFormat(bundle, code, locale); + if (messageFormat != null) { + return messageFormat; + } } } - return messageFormat; + return null; } @@ -265,7 +166,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement * found for the given basename and Locale */ protected ResourceBundle getResourceBundle(String basename, Locale locale) { - if (this.cacheMillis >= 0) { + if (getCacheMillis() >= 0) { // Fresh ResourceBundle.getBundle call in order to let ResourceBundle // do its native caching, at the expense of more extensive lookup steps. return doGetBundle(basename, locale); @@ -404,8 +305,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement */ @Override public String toString() { - return getClass().getName() + ": basenames=[" + - StringUtils.arrayToCommaDelimitedString(this.basenames) + "]"; + return getClass().getName() + ": basenames=" + getBasenameSet(); } @@ -454,8 +354,12 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement throw (IOException) ex.getException(); } if (stream != null) { + String encoding = getDefaultEncoding(); + if (encoding == null) { + encoding = "ISO-8859-1"; + } try { - return loadBundle(new InputStreamReader(stream, defaultEncoding)); + return loadBundle(new InputStreamReader(stream, encoding)); } finally { stream.close(); @@ -473,11 +377,12 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement @Override public Locale getFallbackLocale(String baseName, Locale locale) { - return (fallbackToSystemLocale ? super.getFallbackLocale(baseName, locale) : null); + return (isFallbackToSystemLocale() ? super.getFallbackLocale(baseName, locale) : null); } @Override public long getTimeToLive(String baseName, Locale locale) { + long cacheMillis = getCacheMillis(); return (cacheMillis >= 0 ? cacheMillis : super.getTimeToLive(baseName, locale)); }