Lenient fallback to plain getBundle call without Control handle
Includes defaultEncoding variant for platform default encoding. Issue: SPR-16776
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -115,8 +115,9 @@ public abstract class AbstractResourceBasedMessageSource extends AbstractMessage
|
||||
/**
|
||||
* 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>The effective default is the {@code java.util.Properties}
|
||||
* default encoding: ISO-8859-1. A {@code null} value indicates
|
||||
* the platform default encoding.
|
||||
* <p>Only applies to classic properties files, not to XML files.
|
||||
* @param defaultEncoding the default charset
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,17 @@ import org.springframework.util.ClassUtils;
|
||||
* Note that the JDK's standard ResourceBundle treats dots as package separators:
|
||||
* This means that "test.theme" is effectively equivalent to "test/theme".
|
||||
*
|
||||
* <p>On the classpath, bundle resources will be read with the locally configured
|
||||
* {@link #setDefaultEncoding encoding}: by default, ISO-8859-1; consider switching
|
||||
* this to UTF-8, or to {@code null} for the platform default encoding. On the JDK 9+
|
||||
* module path where locally provided {@link ResourceBundle.Control} handles are not
|
||||
* supported, this MessageSource always falls back to {@link ResourceBundle#getBundle}
|
||||
* retrieval with the platform default encoding: UTF-8 with a ISO-8859-1 fallback on
|
||||
* JDK 9+ (configurable through the "java.util.PropertyResourceBundle.encoding" system
|
||||
* property). Note that {@link #loadBundle(Reader)}/{@link #loadBundle(InputStream)}
|
||||
* won't be called in this case either, effectively ignoring overrides in subclasses.
|
||||
* Consider implementing a JDK 9 {@code java.util.spi.ResourceBundleProvider} instead.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @see #setBasenames
|
||||
@@ -80,7 +91,8 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
* This allows for very efficient hash lookups, significantly faster
|
||||
* than the ResourceBundle class's own cache.
|
||||
*/
|
||||
private final Map<String, Map<Locale, ResourceBundle>> cachedResourceBundles = new ConcurrentHashMap<>();
|
||||
private final Map<String, Map<Locale, ResourceBundle>> cachedResourceBundles =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Cache to hold already generated MessageFormats.
|
||||
@@ -90,7 +102,16 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
* very efficient hash lookups without concatenated keys.
|
||||
* @see #getMessageFormat
|
||||
*/
|
||||
private final Map<ResourceBundle, Map<String, Map<Locale, MessageFormat>>> cachedBundleMessageFormats = new ConcurrentHashMap<>();
|
||||
private final Map<ResourceBundle, Map<String, Map<Locale, MessageFormat>>> cachedBundleMessageFormats =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
@Nullable
|
||||
private volatile MessageSourceControl control = new MessageSourceControl();
|
||||
|
||||
|
||||
public ResourceBundleMessageSource() {
|
||||
setDefaultEncoding("ISO-8859-1");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -220,40 +241,71 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
|
||||
ClassLoader classLoader = getBundleClassLoader();
|
||||
Assert.state(classLoader != null, "No bundle ClassLoader set");
|
||||
String defaultEncoding = getDefaultEncoding();
|
||||
|
||||
if ((defaultEncoding != null && !"ISO-8859-1".equals(defaultEncoding)) ||
|
||||
!isFallbackToSystemLocale() || getCacheMillis() >= 0) {
|
||||
MessageSourceControl control = this.control;
|
||||
if (control != null) {
|
||||
try {
|
||||
return ResourceBundle.getBundle(basename, locale, classLoader, new MessageSourceControl());
|
||||
return ResourceBundle.getBundle(basename, locale, classLoader, control);
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
// Probably in a Jigsaw environment on JDK 9+
|
||||
throw new IllegalStateException(
|
||||
"Custom ResourceBundleMessageSource configuration requires custom ResourceBundle.Control " +
|
||||
"which is not supported in current system environment (e.g. JDK 9+ module path deployment): " +
|
||||
"consider using defaults (ISO-8859-1 encoding, fallback to system locale, unlimited caching)",
|
||||
ex);
|
||||
this.control = null;
|
||||
String encoding = getDefaultEncoding();
|
||||
if (encoding != null && logger.isInfoEnabled()) {
|
||||
logger.info("ResourceBundleMessageSource is configured to read resources with encoding '" +
|
||||
encoding + "' but ResourceBundle.Control not supported in current system environment: " +
|
||||
ex.getMessage() + " - falling back to plain ResourceBundle.getBundle retrieval with the " +
|
||||
"platform default encoding. Consider setting the 'defaultEncoding' property to 'null' " +
|
||||
"for participating in the platform default and therefore avoiding this log message.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ResourceBundle.getBundle(basename, locale, classLoader);
|
||||
}
|
||||
|
||||
// Fallback: plain getBundle lookup without Control handle
|
||||
return ResourceBundle.getBundle(basename, locale, classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a property-based resource bundle from the given reader.
|
||||
* <p>This will be called in case of a {@link #setDefaultEncoding "defaultEncoding"},
|
||||
* including {@link ResourceBundleMessageSource}'s default ISO-8859-1 encoding.
|
||||
* Note that this method can only be called with a {@link ResourceBundle.Control}:
|
||||
* When running on the JDK 9+ module path where such control handles are not
|
||||
* supported, any overrides in custom subclasses will effectively get ignored.
|
||||
* <p>The default implementation returns a {@link PropertyResourceBundle}.
|
||||
* @param reader the reader for the target resource
|
||||
* @return the fully loaded bundle
|
||||
* @throws IOException in case of I/O failure
|
||||
* @since 4.2
|
||||
* @see #loadBundle(InputStream)
|
||||
* @see PropertyResourceBundle#PropertyResourceBundle(Reader)
|
||||
*/
|
||||
protected ResourceBundle loadBundle(Reader reader) throws IOException {
|
||||
return new PropertyResourceBundle(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a property-based resource bundle from the given input stream,
|
||||
* picking up the default properties encoding on JDK 9+.
|
||||
* <p>This will only be called with {@link #setDefaultEncoding "defaultEncoding"}
|
||||
* set to {@code null}, explicitly enforcing the platform default encoding
|
||||
* (which is UTF-8 with a ISO-8859-1 fallback on JDK 9+ but configurable
|
||||
* through the "java.util.PropertyResourceBundle.encoding" system property).
|
||||
* Note that this method can only be called with a {@link ResourceBundle.Control}:
|
||||
* When running on the JDK 9+ module path where such control handles are not
|
||||
* supported, any overrides in custom subclasses will effectively get ignored.
|
||||
* <p>The default implementation returns a {@link PropertyResourceBundle}.
|
||||
* @param inputStream the input stream for the target resource
|
||||
* @return the fully loaded bundle
|
||||
* @throws IOException in case of I/O failure
|
||||
* @since 5.1
|
||||
* @see #loadBundle(Reader)
|
||||
* @see PropertyResourceBundle#PropertyResourceBundle(InputStream)
|
||||
*/
|
||||
protected ResourceBundle loadBundle(InputStream inputStream) throws IOException {
|
||||
return new PropertyResourceBundle(inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a MessageFormat for the given bundle and code,
|
||||
* fetching already generated MessageFormats from the cache.
|
||||
@@ -284,7 +336,8 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
if (msg != null) {
|
||||
if (codeMap == null) {
|
||||
codeMap = new ConcurrentHashMap<>();
|
||||
Map<String, Map<Locale, MessageFormat>> existing = this.cachedBundleMessageFormats.putIfAbsent(bundle, codeMap);
|
||||
Map<String, Map<Locale, MessageFormat>> existing =
|
||||
this.cachedBundleMessageFormats.putIfAbsent(bundle, codeMap);
|
||||
if (existing != null) {
|
||||
codeMap = existing;
|
||||
}
|
||||
@@ -359,9 +412,9 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
final String resourceName = toResourceName(bundleName, "properties");
|
||||
final ClassLoader classLoader = loader;
|
||||
final boolean reloadFlag = reload;
|
||||
InputStream stream;
|
||||
InputStream inputStream;
|
||||
try {
|
||||
stream = AccessController.doPrivileged((PrivilegedExceptionAction<InputStream>) () -> {
|
||||
inputStream = AccessController.doPrivileged((PrivilegedExceptionAction<InputStream>) () -> {
|
||||
InputStream is = null;
|
||||
if (reloadFlag) {
|
||||
URL url = classLoader.getResource(resourceName);
|
||||
@@ -382,13 +435,17 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
catch (PrivilegedActionException ex) {
|
||||
throw (IOException) ex.getException();
|
||||
}
|
||||
if (stream != null) {
|
||||
if (inputStream != null) {
|
||||
String encoding = getDefaultEncoding();
|
||||
if (encoding == null) {
|
||||
encoding = "ISO-8859-1";
|
||||
if (encoding != null) {
|
||||
try (InputStreamReader bundleReader = new InputStreamReader(inputStream, encoding)) {
|
||||
return loadBundle(bundleReader);
|
||||
}
|
||||
}
|
||||
try (InputStreamReader bundleReader = new InputStreamReader(stream, encoding)) {
|
||||
return loadBundle(bundleReader);
|
||||
else {
|
||||
try (InputStream bundleStream = inputStream) {
|
||||
return loadBundle(bundleStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user