(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:
parent
28e8af2e2e
commit
f108deb114
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue