Jason and Kasper recently brought it to my attention that ResourceBundles have some performance issues in web applications. I went through the code and discovered that getInstance() is implemented synchronously. This would definitely degrade performance in any heavily-threaded application which called getInstance() often. This patch for TurbineLocalizationService caches ResourceBundles in an asynchronous manner (the previous implementation used Hashtables, which are also synchonous). This change does not affect the external API of the LocalizationService implementation, and should alleviate the problems encountered when using ResourceBundles in a web app. Index: TurbineLocalizationService.java =================================================================== RCS file: /home/cvs/jakarta-turbine-fulcrum/src/services/java/org/apache/fulcrum/localization/TurbineLocalizationService.java,v retrieving revision 1.3 diff -u -u -r1.3 TurbineLocalizationService.java --- TurbineLocalizationService.java 2001/09/04 23:22:15 1.3 +++ TurbineLocalizationService.java 2001/09/05 06:49:51 @@ -55,7 +55,7 @@ */ import javax.servlet.http.HttpServletRequest; -import java.util.Hashtable; +import java.util.HashMap; import java.util.Locale; import java.util.ResourceBundle; import java.util.StringTokenizer; @@ -89,6 +89,7 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Jonas Maurus</a> * @author <a href="mailto:[EMAIL PROTECTED]">Jon S. Stevens</a> * @author <a href="mailto:[EMAIL PROTECTED]">Frank Y. Kim</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Daniel Rall</a> * @version $Id: TurbineLocalizationService.java,v 1.3 2001/09/04 23:22:15 dlr Exp $ */ public class TurbineLocalizationService @@ -96,11 +97,10 @@ implements LocalizationService { /** - * The ResourceBundles in this service. - * Key=bundle name - * Value=Hashtable containing ResourceBundles keyed by Locale. + * Bundle name keys a HashMap of the ResourceBundles in this + * service (which is in turn keyed by Locale). */ - private static Hashtable bundles = null; + private static HashMap bundles = null; /** The name of the default bundle to use. */ private static String defaultBundle = null; @@ -124,7 +124,7 @@ public void init() throws InitializationException { - bundles = new Hashtable(); + bundles = new HashMap(); defaultBundle = getConfiguration().getString("locale.default.bundle"); defaultLanguage = getConfiguration() .getString("locale.default.language", "en").trim(); @@ -241,45 +241,69 @@ Locale locale) { bundleName = bundleName.trim(); + ResourceBundle rb = (ResourceBundle) bundles.get(bundleName); + HashMap bundlesByLocale = (HashMap) bundles.get(bundleName); - if ( bundles.containsKey(bundleName) ) + if (bundlesByLocale != null) { - Hashtable locales = (Hashtable)bundles.get(bundleName); + rb = (ResourceBundle) bundlesByLocale.get(locale); - if ( locales.containsKey(locale) ) + if (rb == null) { - return (ResourceBundle)locales.get(locale); + // Try to create a ResourceBundle for this Locale. + rb = cacheBundle(bundleName, locale); } - else + } + else + { + synchronized (bundles) { - // Try to create a ResourceBundle for this Locale. - ResourceBundle rb = - ResourceBundle.getBundle(bundleName, locale); - - // Cache the ResourceBundle in memory. - locales.put( rb.getLocale(), rb ); + bundlesByLocale = (HashMap) bundles.get(bundleName); - return rb; + if (bundlesByLocale == null) + { + // Create a ResourceBundle for requested Locale + // and cache in memory. + rb = cacheBundle(bundleName, locale); + } } } - else + + return rb; + } + + /** + * Caches the named bundle for fast lookups. This operation is + * relatively expesive in terms of memory use, but is optimized + * for run-time speed. + */ + private ResourceBundle cacheBundle(String bundleName, Locale locale) + { + ResourceBundle rb = null; + synchronized (bundles) { - // Create a ResourceBundle for requested Locale. - ResourceBundle rb = - ResourceBundle.getBundle(bundleName, locale); - - // Cache the ResourceBundle in memory. - Hashtable bundlesByLocale = new Hashtable(); - bundlesByLocale.put(locale, rb); - - // Can't call getLocale(), because that is jdk2. This - // needs to be changed back, since the above approach - // caches extra Locale and Bundle objects. - // bundlesByLocale.put( rb.getLocale(), rb ); - bundles.put(bundleName, bundlesByLocale); + HashMap bundlesByLocale = (HashMap) bundles.get(bundleName); + rb = (bundlesByLocale == null ? null : + (ResourceBundle) bundlesByLocale.get(bundleName)); + + if (rb == null) + { + HashMap bundlesByName = new HashMap(bundles); + bundlesByLocale = (bundlesByLocale == null ? new HashMap(3) : + new HashMap(bundlesByLocale)); + rb = ResourceBundle.getBundle(bundleName, locale); + + // Can't call getLocale(), because that is jdk2. This + // needs to be changed back, since the above approach + // caches extra Locale and Bundle objects. + // bundlesByLocale.put( rb.getLocale(), rb ); + bundlesByLocale.put(locale, rb); - return rb; + bundlesByName.put(bundleName, bundlesByLocale); + this.bundles = bundlesByName; + } } + return rb; } /** --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
