Lenient fallback to plain getBundle call without Control handle

Includes defaultEncoding variant for platform default encoding.

Issue: SPR-16776
This commit is contained in:
Juergen Hoeller
2018-05-01 23:50:34 +02:00
parent ac1e2a4598
commit ad0d79a97b
2 changed files with 83 additions and 25 deletions

View File

@@ -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
*/

View File

@@ -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 {