WW-4762 Extracts base abstract class
Project: http://git-wip-us.apache.org/repos/asf/struts/repo Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/2e23d7a0 Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/2e23d7a0 Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/2e23d7a0 Branch: refs/heads/default-provider Commit: 2e23d7a07ea4313136dc38b8de2451b04c882064 Parents: 8bf77a1 Author: Lukasz Lenart <lukaszlen...@apache.org> Authored: Wed Apr 26 12:24:57 2017 +0200 Committer: Lukasz Lenart <lukaszlen...@apache.org> Committed: Wed Apr 26 12:24:57 2017 +0200 ---------------------------------------------------------------------- .../util/AbstractLocalizedTextProvider.java | 355 ++++++++++++++++++ .../util/StrutsLocalizedTextProvider.java | 368 +------------------ 2 files changed, 363 insertions(+), 360 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/struts/blob/2e23d7a0/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java new file mode 100644 index 0000000..a2578bc --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java @@ -0,0 +1,355 @@ +package com.opensymphony.xwork2.util; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.LocalizedTextProvider; +import com.opensymphony.xwork2.inject.Inject; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.StrutsConstants; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { + + private static final Logger LOG = LogManager.getLogger(AbstractLocalizedTextProvider.class); + + public static final String XWORK_MESSAGES_BUNDLE = "com/opensymphony/xwork2/xwork-messages"; + public static final String STRUTS_MESSAGES_BUNDLE = "org/apache/struts2/struts-messages"; + + private static final String TOMCAT_RESOURCE_ENTRIES_FIELD = "resourceEntries"; + private final String RELOADED = "com.opensymphony.xwork2.util.LocalizedTextProvider.reloaded"; + + protected final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<>(); + protected boolean devMode = false; + protected boolean reloadBundles = false; + + private final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<>(); + private final ConcurrentMap<Integer, List<String>> classLoaderMap = new ConcurrentHashMap<>(); + private final Set<String> missingBundles = Collections.synchronizedSet(new HashSet<String>()); + private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>(); + + /** + * Add's the bundle to the internal list of default bundles. + * If the bundle already exists in the list it will be re-added. + * + * @param resourceBundleName the name of the bundle to add. + */ + @Override + public void addDefaultResourceBundle(String resourceBundleName) { + //make sure this doesn't get added more than once + final ClassLoader ccl = getCurrentThreadContextClassLoader(); + synchronized (XWORK_MESSAGES_BUNDLE) { + List<String> bundles = classLoaderMap.get(ccl.hashCode()); + if (bundles == null) { + bundles = new CopyOnWriteArrayList<>(); + classLoaderMap.put(ccl.hashCode(), bundles); + } + bundles.remove(resourceBundleName); + bundles.add(0, resourceBundleName); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", resourceBundleName, ccl.toString()); + } + } + + protected List<String> getCurrentBundleNames() { + return classLoaderMap.get(getCurrentThreadContextClassLoader().hashCode()); + } + + protected ClassLoader getCurrentThreadContextClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + /** + * Returns a localized message for the specified key, aTextName. Neither the key nor the + * message is evaluated. + * + * @param aTextName the message key + * @param locale the locale the message should be for + * @return a localized message based on the specified key, or null if no localized message can be found for it + */ + @Override + public String findDefaultText(String aTextName, Locale locale) { + List<String> localList = getCurrentBundleNames(); + + for (String bundleName : localList) { + ResourceBundle bundle = findResourceBundle(bundleName, locale); + if (bundle != null) { + reloadBundles(); + try { + return bundle.getString(aTextName); + } catch (MissingResourceException e) { + // will be logged when not found in any bundle + } + } + } + + if (devMode) { + LOG.warn("Missing key [{}] in bundles [{}]!", aTextName, localList); + } else { + LOG.debug("Missing key [{}] in bundles [{}]!", aTextName, localList); + } + + return null; + } + + /** + * Returns a localized message for the specified key, aTextName, substituting variables from the + * array of params into the message. Neither the key nor the message is evaluated. + * + * @param aTextName the message key + * @param locale the locale the message should be for + * @param params an array of objects to be substituted into the message text + * @return A formatted message based on the specified key, or null if no localized message can be found for it + */ + @Override + public String findDefaultText(String aTextName, Locale locale, Object[] params) { + String defaultText = findDefaultText(aTextName, locale); + if (defaultText != null) { + MessageFormat mf = buildMessageFormat(defaultText, locale); + return formatWithNullDetection(mf, params); + } + return null; + } + + /** + * @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader + */ + public void setDelegatedClassLoader(final ClassLoader classLoader) { + synchronized (bundlesMap) { + delegatedClassLoaderMap.put(getCurrentThreadContextClassLoader().hashCode(), classLoader); + } + } + + /** + * @param bundleName Removes the bundle from any cached "misses" + */ + public void clearBundle(final String bundleName) { + bundlesMap.remove(getCurrentThreadContextClassLoader().hashCode() + bundleName); + } + + protected void reloadBundles() { + reloadBundles(ActionContext.getContext() != null ? ActionContext.getContext().getContextMap() : null); + } + + protected void reloadBundles(Map<String, Object> context) { + if (reloadBundles) { + try { + Boolean reloaded; + if (context != null) { + reloaded = (Boolean) ObjectUtils.defaultIfNull(context.get(RELOADED), Boolean.FALSE); + } else { + reloaded = Boolean.FALSE; + } + if (!reloaded) { + bundlesMap.clear(); + try { + clearMap(ResourceBundle.class, null, "cacheList"); + } catch (NoSuchFieldException e) { + // happens in IBM JVM, that has a different ResourceBundle impl + // it has a 'cache' member + clearMap(ResourceBundle.class, null, "cache"); + } + + // now, for the true and utter hack, if we're running in tomcat, clear + // it's class loader resource cache as well. + clearTomcatCache(); + if (context != null) { + context.put(RELOADED, true); + } + LOG.debug("Resource bundles reloaded"); + } + } catch (Exception e) { + LOG.error("Could not reload resource bundles", e); + } + } + } + + private void clearTomcatCache() { + ClassLoader loader = getCurrentThreadContextClassLoader(); + // no need for compilation here. + Class cl = loader.getClass(); + + try { + if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) { + clearMap(cl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD); + } else { + LOG.debug("Class loader {} is not tomcat loader.", cl.getName()); + } + } catch (NoSuchFieldException nsfe) { + if ("org.apache.catalina.loader.WebappClassLoaderBase".equals(cl.getSuperclass().getName())) { + LOG.debug("Base class {} doesn't contain '{}' field, trying with parent!", cl.getName(), TOMCAT_RESOURCE_ENTRIES_FIELD, nsfe); + try { + clearMap(cl.getSuperclass(), loader, TOMCAT_RESOURCE_ENTRIES_FIELD); + } catch (Exception e) { + LOG.warn("Couldn't clear tomcat cache using {}", cl.getSuperclass().getName(), e); + } + } + } catch (Exception e) { + LOG.warn("Couldn't clear tomcat cache", cl.getName(), e); + } + } + + private void clearMap(Class cl, Object obj, String name) + throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + + Field field = cl.getDeclaredField(name); + field.setAccessible(true); + + Object cache = field.get(obj); + + synchronized (cache) { + Class ccl = cache.getClass(); + Method clearMethod = ccl.getMethod("clear"); + clearMethod.invoke(cache); + } + } + + protected MessageFormat buildMessageFormat(String pattern, Locale locale) { + MessageFormatKey key = new MessageFormatKey(pattern, locale); + MessageFormat format = messageFormats.get(key); + if (format == null) { + format = new MessageFormat(pattern); + format.setLocale(locale); + format.applyPattern(pattern); + messageFormats.put(key, format); + } + + return format; + } + + protected String formatWithNullDetection(MessageFormat mf, Object[] args) { + String message = mf.format(args); + if ("null".equals(message)) { + return null; + } else { + return message; + } + } + + @Inject(value = StrutsConstants.STRUTS_I18N_RELOAD, required = false) + public void setReloadBundles(String reloadBundles) { + this.reloadBundles = Boolean.parseBoolean(reloadBundles); + } + + @Inject(value = StrutsConstants.STRUTS_DEVMODE, required = false) + public void setDevMode(String devMode) { + this.devMode = Boolean.parseBoolean(devMode); + } + + /** + * Finds the given resource bundle by it's name. + * <p> + * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader. + * </p> + * + * @param aBundleName the name of the bundle (usually it's FQN classname). + * @param locale the locale. + * @return the bundle, <tt>null</tt> if not found. + */ + @Override + public ResourceBundle findResourceBundle(String aBundleName, Locale locale) { + ClassLoader classLoader = getCurrentThreadContextClassLoader(); + String key = createMissesKey(String.valueOf(classLoader.hashCode()), aBundleName, locale); + + if (missingBundles.contains(key)) { + return null; + } + + ResourceBundle bundle = null; + try { + if (bundlesMap.containsKey(key)) { + bundle = bundlesMap.get(key); + } else { + bundle = ResourceBundle.getBundle(aBundleName, locale, classLoader); + bundlesMap.putIfAbsent(key, bundle); + } + } catch (MissingResourceException ex) { + if (delegatedClassLoaderMap.containsKey(classLoader.hashCode())) { + try { + if (bundlesMap.containsKey(key)) { + bundle = bundlesMap.get(key); + } else { + bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode())); + bundlesMap.putIfAbsent(key, bundle); + } + } catch (MissingResourceException e) { + LOG.debug("Missing resource bundle [{}]!", aBundleName, e); + missingBundles.add(key); + } + } else { + LOG.debug("Missing resource bundle [{}]!", aBundleName); + missingBundles.add(key); + } + } + return bundle; + } + + /** + * Creates a key to used for lookup/storing in the bundle misses cache. + * + * @param prefix the prefix for the returning String - it is supposed to be the ClassLoader hash code. + * @param aBundleName the name of the bundle (usually it's FQN classname). + * @param locale the locale. + * @return the key to use for lookup/storing in the bundle misses cache. + */ + private String createMissesKey(String prefix, String aBundleName, Locale locale) { + return prefix + aBundleName + "_" + locale.toString(); + } + + static class MessageFormatKey { + String pattern; + Locale locale; + + MessageFormatKey(String pattern, Locale locale) { + this.pattern = pattern; + this.locale = locale; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MessageFormatKey that = (MessageFormatKey) o; + + if (pattern != null ? !pattern.equals(that.pattern) : that.pattern != null) return false; + return locale != null ? locale.equals(that.locale) : that.locale == null; + } + + @Override + public int hashCode() { + int result = pattern != null ? pattern.hashCode() : 0; + result = 31 * result + (locale != null ? locale.hashCode() : 0); + return result; + } + } + + static class GetDefaultMessageReturnArg { + String message; + boolean foundInBundle; + + public GetDefaultMessageReturnArg(String message, boolean foundInBundle) { + this.message = message; + this.foundInBundle = foundInBundle; + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/2e23d7a0/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java index 78bf08d..3d03f9a 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java @@ -23,52 +23,32 @@ package com.opensymphony.xwork2.util; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.LocalizedTextProvider; import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.conversion.impl.XWorkConverter; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory; -import org.apache.commons.lang3.ObjectUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.struts2.StrutsConstants; import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.text.MessageFormat; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeSet; /** * Provides support for localization in the framework, it can be used to read only default bundles, * or it can search the class hierarchy to find proper bundles. */ -public class StrutsLocalizedTextProvider implements LocalizedTextProvider { +public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider { private static final Logger LOG = LogManager.getLogger(StrutsLocalizedTextProvider.class); - public static final String XWORK_MESSAGES_BUNDLE = "com/opensymphony/xwork2/xwork-messages"; - public static final String STRUTS_MESSAGES_BUNDLE = "org/apache/struts2/struts-messages"; - - private static final String TOMCAT_RESOURCE_ENTRIES_FIELD = "resourceEntries"; - - private final ConcurrentMap<Integer, List<String>> classLoaderMap = new ConcurrentHashMap<>(); - - private boolean reloadBundles = false; - private boolean devMode = false; - - private final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<>(); - private final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<>(); - private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>(); - private final Set<String> missingBundles = Collections.synchronizedSet(new HashSet<String>()); - - private final String RELOADED = "com.opensymphony.xwork2.util.LocalizedTextUtil.reloaded"; - /** * Clears the internal list of resource bundles. * @@ -80,26 +60,10 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider { } public StrutsLocalizedTextProvider() { - addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE); addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE); } - /** - * Should resorce bundles be reloaded. - * - * @param reloadBundles reload bundles? - */ - @Inject(value = StrutsConstants.STRUTS_I18N_RELOAD, required = false) - public void setReloadBundles(String reloadBundles) { - this.reloadBundles = Boolean.parseBoolean(reloadBundles); - } - - @Inject(value = StrutsConstants.STRUTS_DEVMODE, required = false) - public void setDevMode(String devMode) { - this.devMode = Boolean.parseBoolean(devMode); - } - @Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false) public void setCustomI18NResources(String bundles) { if (bundles != null && bundles.length() > 0) { @@ -118,33 +82,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider { } /** - * Add's the bundle to the internal list of default bundles. - * <p> - * If the bundle already exists in the list it will be readded. - * </p> - * - * @param resourceBundleName the name of the bundle to add. - */ - @Override - public void addDefaultResourceBundle(String resourceBundleName) { - //make sure this doesn't get added more than once - final ClassLoader ccl = getCurrentThreadContextClassLoader(); - synchronized (XWORK_MESSAGES_BUNDLE) { - List<String> bundles = classLoaderMap.get(ccl.hashCode()); - if (bundles == null) { - bundles = new CopyOnWriteArrayList<>(); - classLoaderMap.put(ccl.hashCode(), bundles); - } - bundles.remove(resourceBundleName); - bundles.add(0, resourceBundleName); - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", resourceBundleName, ccl.toString()); - } - } - - /** * Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale * with language "en", country "US" and variant "foo". This will parse the output of * {@link java.util.Locale#toString()}. @@ -190,135 +127,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider { } /** - * Returns a localized message for the specified key, aTextName. Neither the key nor the - * message is evaluated. - * - * @param aTextName the message key - * @param locale the locale the message should be for - * @return a localized message based on the specified key, or null if no localized message can be found for it - */ - @Override - public String findDefaultText(String aTextName, Locale locale) { - List<String> localList = classLoaderMap.get(Thread.currentThread().getContextClassLoader().hashCode()); - - for (String bundleName : localList) { - ResourceBundle bundle = findResourceBundle(bundleName, locale); - if (bundle != null) { - reloadBundles(); - try { - return bundle.getString(aTextName); - } catch (MissingResourceException e) { - // will be logged when not found in any bundle - } - } - } - - if (devMode) { - LOG.warn("Missing key [{}] in bundles [{}]!", aTextName, localList); - } else { - LOG.debug("Missing key [{}] in bundles [{}]!", aTextName, localList); - } - - return null; - } - - /** - * Returns a localized message for the specified key, aTextName, substituting variables from the - * array of params into the message. Neither the key nor the message is evaluated. - * - * @param aTextName the message key - * @param locale the locale the message should be for - * @param params an array of objects to be substituted into the message text - * @return A formatted message based on the specified key, or null if no localized message can be found for it - */ - @Override - public String findDefaultText(String aTextName, Locale locale, Object[] params) { - String defaultText = findDefaultText(aTextName, locale); - if (defaultText != null) { - MessageFormat mf = buildMessageFormat(defaultText, locale); - return formatWithNullDetection(mf, params); - } - return null; - } - - /** - * Finds the given resource bundle by it's name. - * <p> - * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader. - * </p> - * - * @param aBundleName the name of the bundle (usually it's FQN classname). - * @param locale the locale. - * @return the bundle, <tt>null</tt> if not found. - */ - @Override - public ResourceBundle findResourceBundle(String aBundleName, Locale locale) { - ClassLoader classLoader = getCurrentThreadContextClassLoader(); - String key = createMissesKey(String.valueOf(classLoader.hashCode()), aBundleName, locale); - - if (missingBundles.contains(key)) { - return null; - } - - ResourceBundle bundle = null; - try { - if (bundlesMap.containsKey(key)) { - bundle = bundlesMap.get(key); - } else { - bundle = ResourceBundle.getBundle(aBundleName, locale, classLoader); - bundlesMap.putIfAbsent(key, bundle); - } - } catch (MissingResourceException ex) { - if (delegatedClassLoaderMap.containsKey(classLoader.hashCode())) { - try { - if (bundlesMap.containsKey(key)) { - bundle = bundlesMap.get(key); - } else { - bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode())); - bundlesMap.putIfAbsent(key, bundle); - } - } catch (MissingResourceException e) { - LOG.debug("Missing resource bundle [{}]!", aBundleName, e); - missingBundles.add(key); - } - } else { - LOG.debug("Missing resource bundle [{}]!", aBundleName); - missingBundles.add(key); - } - } - return bundle; - } - - /** - * @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader - */ - public void setDelegatedClassLoader(final ClassLoader classLoader) { - synchronized (bundlesMap) { - delegatedClassLoaderMap.put(getCurrentThreadContextClassLoader().hashCode(), classLoader); - } - } - - /** - * @param bundleName Removes the bundle from any cached "misses" - */ - public void clearBundle(final String bundleName) { - bundlesMap.remove(getCurrentThreadContextClassLoader().hashCode() + bundleName); - } - - - /** - * Creates a key to used for lookup/storing in the bundle misses cache. - * - * @param prefix the prefix for the returning String - it is supposed to be the ClassLoader hash code. - * @param aBundleName the name of the bundle (usually it's FQN classname). - * @param locale the locale. - * @return the key to use for lookup/storing in the bundle misses cache. - */ - private String createMissesKey(String prefix, String aBundleName, Locale locale) { - return prefix + aBundleName + "_" + locale.toString(); - } - - /** * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)} * with aTextName as the default message. * @@ -759,28 +567,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider { } } - private String formatWithNullDetection(MessageFormat mf, Object[] args) { - String message = mf.format(args); - if ("null".equals(message)) { - return null; - } else { - return message; - } - } - - private MessageFormat buildMessageFormat(String pattern, Locale locale) { - MessageFormatKey key = new MessageFormatKey(pattern, locale); - MessageFormat format = messageFormats.get(key); - if (format == null) { - format = new MessageFormat(pattern); - format.setLocale(locale); - format.applyPattern(pattern); - messageFormats.put(key, format); - } - - return format; - } - /** * Traverse up class hierarchy looking for message. Looks at class, then implemented interface, * before going up hierarchy. @@ -790,7 +576,7 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider { private String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked, ValueStack valueStack) { if (checked == null) { - checked = new TreeSet<String>(); + checked = new TreeSet<>(); } else if (checked.contains(clazz.getName())) { return null; } @@ -849,85 +635,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider { return null; } - private void reloadBundles() { - reloadBundles(ActionContext.getContext() != null ? ActionContext.getContext().getContextMap() : null); - } - - private void reloadBundles(Map<String, Object> context) { - if (reloadBundles) { - try { - Boolean reloaded; - if (context != null) { - reloaded = (Boolean) ObjectUtils.defaultIfNull(context.get(RELOADED), Boolean.FALSE); - }else { - reloaded = Boolean.FALSE; - } - if (!reloaded) { - bundlesMap.clear(); - try { - clearMap(ResourceBundle.class, null, "cacheList"); - } catch (NoSuchFieldException e) { - // happens in IBM JVM, that has a different ResourceBundle impl - // it has a 'cache' member - clearMap(ResourceBundle.class, null, "cache"); - } - - // now, for the true and utter hack, if we're running in tomcat, clear - // it's class loader resource cache as well. - clearTomcatCache(); - if(context!=null) { - context.put(RELOADED, true); - } - LOG.debug("Resource bundles reloaded"); - } - } catch (Exception e) { - LOG.error("Could not reload resource bundles", e); - } - } - } - - - private void clearTomcatCache() { - ClassLoader loader = getCurrentThreadContextClassLoader(); - // no need for compilation here. - Class cl = loader.getClass(); - - try { - if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) { - clearMap(cl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD); - } else { - LOG.debug("Class loader {} is not tomcat loader.", cl.getName()); - } - } catch (NoSuchFieldException nsfe) { - if ("org.apache.catalina.loader.WebappClassLoaderBase".equals(cl.getSuperclass().getName())) { - LOG.debug("Base class {} doesn't contain '{}' field, trying with parent!", cl.getName(), TOMCAT_RESOURCE_ENTRIES_FIELD, nsfe); - try { - clearMap(cl.getSuperclass(), loader, TOMCAT_RESOURCE_ENTRIES_FIELD); - } catch (Exception e) { - LOG.warn("Couldn't clear tomcat cache using {}", cl.getSuperclass().getName(), e); - } - } - } catch (Exception e) { - LOG.warn("Couldn't clear tomcat cache", cl.getName(), e); - } - } - - - private void clearMap(Class cl, Object obj, String name) - throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - - Field field = cl.getDeclaredField(name); - field.setAccessible(true); - - Object cache = field.get(obj); - - synchronized (cache) { - Class ccl = cache.getClass(); - Method clearMethod = ccl.getMethod("clear"); - clearMethod.invoke(cache); - } - } - /** * Clears all the internal lists. * @@ -938,63 +645,4 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider { // no-op } - static class MessageFormatKey { - String pattern; - Locale locale; - - MessageFormatKey(String pattern, Locale locale) { - this.pattern = pattern; - this.locale = locale; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof MessageFormatKey)) return false; - - final MessageFormatKey messageFormatKey = (MessageFormatKey) o; - - if (locale != null ? !locale.equals(messageFormatKey.locale) : messageFormatKey.locale != null) - return false; - if (pattern != null ? !pattern.equals(messageFormatKey.pattern) : messageFormatKey.pattern != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result; - result = (pattern != null ? pattern.hashCode() : 0); - result = 29 * result + (locale != null ? locale.hashCode() : 0); - return result; - } - } - - private static ClassLoader getCurrentThreadContextClassLoader() { - return Thread.currentThread().getContextClassLoader(); - } - - static class GetDefaultMessageReturnArg { - String message; - boolean foundInBundle; - - public GetDefaultMessageReturnArg(String message, boolean foundInBundle) { - this.message = message; - this.foundInBundle = foundInBundle; - } - } - - private static class EmptyResourceBundle extends ResourceBundle { - @Override - public Enumeration<String> getKeys() { - return null; // dummy - } - - @Override - protected Object handleGetObject(String key) { - return null; // dummy - } - } - }