(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
This commit is contained in:
Juergen Hoeller 2016-02-04 19:57:40 +01:00
parent 28e8af2e2e
commit f108deb114
3 changed files with 265 additions and 260 deletions

View File

@ -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<String> basenameSet = new LinkedHashSet<String>(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.
* <p>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.
* <p>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.
* <p>The associated resource bundles will be checked sequentially when resolving
* a message code. Note that message definitions in a <i>previous</i> resource
* bundle will override ones in a later bundle, due to the sequential lookup.
* <p>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.
* <p>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.
* <p>Calling code may introspect this set as well as add or remove entries.
* @since 4.3
* @see #addBasenames
*/
public Set<String> 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.
* <p>Default is none, using the {@code java.util.Properties}
* default encoding: ISO-8859-1.
* <p>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").
* <p>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.
* <ul>
* <li>Default is "-1", indicating to cache forever (just like
* {@code java.util.ResourceBundle}).
* <li>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.
* <li>A value of "0" will check the last-modified timestamp of the file on
* every message access. <b>Do not use this in a production environment!</b>
* </ul>
* <p><b>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.</b>
* 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}.
* <ul>
* <li>Default is "-1", indicating to cache forever (just like
* {@code java.util.ResourceBundle}).
* <li>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.
* <li>A value of "0" will check the last-modified timestamp of the file on
* every message access. <b>Do not use this in a production environment!</b>
* </ul>
* @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;
}
}

View File

@ -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.
*
* <p>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 <i>not</i> be reflected in the application.
*
* <p>Note that the base names set as {@link #setBasenames "basenames"} property
* <p>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.
*
* <p>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 <i>do not</i> make a significant difference.
* other than "-1" (caching forever) might not work reliably in this case.
*
* <p>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 <i>previous</i>
* resource bundle will override ones in a later bundle, due to sequential lookup.
* <p>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<Locale, PropertiesHolder>();
/**
* 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.
* <p>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.
* <p>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.
* <p>The associated resource bundles will be checked sequentially when resolving
* a message code. Note that message definitions in a <i>previous</i> 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.
* <p>Default is none, using the {@code java.util.Properties}
* default encoding: ISO-8859-1.
* <p>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.
* <p>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").
* <p>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.
* <ul>
* <li>Default is "-1", indicating to cache forever (just like
* {@code java.util.ResourceBundle}).
* <li>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.
* <li>A value of "0" will check the last-modified timestamp of the file on
* every message access. <b>Do not use this in a production environment!</b>
* </ul>
*/
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
* <p>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<String> 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<String> 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<String> filenames = calculateAllFilenames(this.basenames[i], locale);
String[] basenames = StringUtils.toStringArray(getBasenameSet());
for (int i = basenames.length - 1; i >= 0; i--) {
List<String> 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<String> filenames = new ArrayList<String>(7);
filenames.addAll(calculateFilenamesForLocale(basename, locale));
if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) {
if (isFallbackToSystemLocale() && !locale.equals(Locale.getDefault())) {
List<String> 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();
}

View File

@ -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.
*
* <p>Unfortunately, {@code java.util.ResourceBundle} caches loaded bundles
* forever: Reloading a bundle during VM execution is <i>not</i> 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.
* <p>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<ResourceBundle, Map<String, Map<Locale, MessageFormat>>>();
/**
* 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.
* <p>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.
* <p>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.
* <p>The associated resource bundles will be checked sequentially
* when resolving a message code. Note that message definitions in a
* <i>previous</i> resource bundle will override ones in a later bundle,
* due to the sequential lookup.
* <p>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.
* <p>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").
* <p>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.
* <ul>
* <li>Default is "-1", indicating to cache forever.
* <li>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.
* <li>A value of "0" will check the last-modified timestamp of the file on
* every message access. <b>Do not use this in a production environment!</b>
* <li><b>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.</b>
* Consider {@link ReloadableResourceBundleMessageSource} in combination
* with resource bundle files in a non-classpath location.
* </ul>
* @since 3.1.3
*/
public void setCacheSeconds(int cacheSeconds) {
this.cacheMillis = (cacheSeconds * 1000);
}
/**
* Set the ClassLoader to load resource bundles with.
* <p>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<String> 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<String> 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));
}