[JENKINS-72449] Specify that no fallback to the default locale should be used when looking up a resource bundle via I18n action. (#8776)

[JENKINS-72449] Specify that no fallback to the default locale should be used when looking up a resource bundle

When running the JVM with a default locale that is not english, the
resource bundle lookup for english would return a bundle with that
default locale, instead of using the "default" that is english.

Also changed bundle resolution to use uberClassloader rather than
iterating on all plugin classloaders
This commit is contained in:
Vincent Latombe authored and GitHub committed 2023-12-14 21:35:49 +01:00
1 parent 302e6ac2d1
commit 259ccc06fb
2 files changed
+40 -60

No files matched your search

@@ -24,15 +24,12 @@
package jenkins.util;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.PluginWrapper;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
@@ -44,9 +41,8 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
* @since 2.0
*/
@Restricted(NoExternalUse.class)
public class ResourceBundleUtil {
private static final Logger logger = Logger.getLogger("jenkins.util.ResourceBundle");
public final class ResourceBundleUtil {
// TODO proper cache eviction
private static final Map<String, JSONObject> bundles = new ConcurrentHashMap<>();
private ResourceBundleUtil() {
@@ -70,55 +66,20 @@ public class ResourceBundleUtil {
* @throws MissingResourceException Missing resource bundle.
*/
public static @NonNull JSONObject getBundle(@NonNull String baseName, @NonNull Locale locale) throws MissingResourceException {
String bundleKey = baseName + ":" + locale;
JSONObject bundleJSON = bundles.get(bundleKey);
var bundleKey = baseName + ":" + locale;
var bundleJSON = bundles.get(bundleKey);
if (bundleJSON != null) {
return bundleJSON;
}
ResourceBundle bundle = getBundle(baseName, locale, Jenkins.class.getClassLoader());
if (bundle == null) {
// Not in Jenkins core. Check the plugins.
Jenkins jenkins = Jenkins.getInstanceOrNull();
if (jenkins != null) {
for (PluginWrapper plugin : jenkins.getPluginManager().getPlugins()) {
bundle = getBundle(baseName, locale, plugin.classLoader);
if (bundle != null) {
break;
}
}
}
}
if (bundle == null) {
throw new MissingResourceException("Can't find bundle for base name "
+ baseName + ", locale " + locale, baseName + "_" + locale, "");
}
bundleJSON = toJSONObject(bundle);
var noFallbackControl = ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT);
var uberClassLoader = Jenkins.get().getPluginManager().uberClassLoader;
bundleJSON = toJSONObject(ResourceBundle.getBundle(baseName, locale, uberClassLoader, noFallbackControl));
bundles.put(bundleKey, bundleJSON);
return bundleJSON;
}
/**
* Get a plugin bundle using the supplied Locale and classLoader
*
* @param baseName The bundle base name.
* @param locale The Locale.
* @param classLoader The classLoader
* @return The bundle JSON.
*/
private static @CheckForNull ResourceBundle getBundle(@NonNull String baseName, @NonNull Locale locale, @NonNull ClassLoader classLoader) {
try {
return ResourceBundle.getBundle(baseName, locale, classLoader);
} catch (MissingResourceException e) {
// fall through and return null.
logger.finer(e.getMessage());
}
return null;
}
/**
* Create a JSON representation of a resource bundle
*
@@ -24,18 +24,23 @@
package jenkins.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import java.util.Locale;
import java.util.MissingResourceException;
import net.sf.json.JSONObject;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
/**
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
*/
public class ResourceBundleUtilTest {
@Rule
public JenkinsRule j = new JenkinsRule();
/**
* Test resource bundle loading for a defined locale.
@@ -43,12 +48,20 @@ public class ResourceBundleUtilTest {
@Test
public void test_known_locale() {
JSONObject bundle = ResourceBundleUtil.getBundle("hudson.logging.Messages", Locale.GERMAN);
Assert.assertEquals("Initialisiere Log-Rekorder", bundle.getString("LogRecorderManager.init"));
assertEquals("Initialisiere Log-Rekorder", bundle.getString("LogRecorderManager.init"));
bundle = ResourceBundleUtil.getBundle("hudson.logging.Messages", new Locale("de"));
Assert.assertEquals("Initialisiere Log-Rekorder", bundle.getString("LogRecorderManager.init"));
assertEquals("Initialisiere Log-Rekorder", bundle.getString("LogRecorderManager.init"));
// Test caching - should get the same bundle instance back...
Assert.assertSame(ResourceBundleUtil.getBundle("hudson.logging.Messages", new Locale("de")), bundle);
assertSame(ResourceBundleUtil.getBundle("hudson.logging.Messages", new Locale("de")), bundle);
}
@Test
public void noFallbackLocale() {
try (var ignored = new DefaultLocale(new Locale("fr"))) {
var bundle = ResourceBundleUtil.getBundle("hudson.logging.Messages", new Locale("en"));
assertEquals("System Log", bundle.getString("LogRecorderManager.DisplayName"));
}
}
/**
@@ -56,16 +69,8 @@ public class ResourceBundleUtilTest {
*/
@Test
public void test_unknown_locale() {
Locale defaultOSLocale = Locale.getDefault();
try {
//Set Default-Locale to english
Locale.setDefault(new Locale("en", "US"));
JSONObject bundle = ResourceBundleUtil.getBundle("hudson.logging.Messages", new Locale("kok")); // konkani
Assert.assertEquals("Initializing log recorders", bundle.getString("LogRecorderManager.init"));
} finally {
Locale.setDefault(defaultOSLocale);
}
JSONObject bundle = ResourceBundleUtil.getBundle("hudson.logging.Messages", new Locale("kok")); // konkani
assertEquals("Initializing log recorders", bundle.getString("LogRecorderManager.init"));
}
/**
@@ -75,4 +80,18 @@ public class ResourceBundleUtilTest {
public void test_unknown_bundle() {
assertThrows(MissingResourceException.class, () -> ResourceBundleUtil.getBundle("hudson.blah.Whatever"));
}
private static class DefaultLocale implements AutoCloseable {
private Locale previous;
DefaultLocale(Locale locale) {
previous = Locale.getDefault();
Locale.setDefault(locale);
}
@Override
public void close() {
Locale.setDefault(previous);
}
}
}