Skip to content

Commit

Permalink
[JENKINS-72449] Specify that no fallback to the default locale should…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
Vlatombe authored Dec 14, 2023
1 parent 302e6ac commit 259ccc0
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 60 deletions.
53 changes: 7 additions & 46 deletions core/src/main/java/jenkins/util/ResourceBundleUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,9 +41,8 @@
* @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() {
Expand All @@ -70,55 +66,20 @@ private 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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,48 +24,53 @@

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:[email protected]">[email protected]</a>
*/
public class ResourceBundleUtilTest {
@Rule
public JenkinsRule j = new JenkinsRule();

/**
* Test resource bundle loading for a defined locale.
*/
@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"));
}
}

/**
* Test that we get the "default" bundle for an unknown locale.
*/
@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"));
}

/**
Expand All @@ -75,4 +80,18 @@ public void test_unknown_locale() {
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);
}
}
}

0 comments on commit 259ccc0

Please sign in to comment.