You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2017/05/18 08:09:43 UTC

[01/10] struts git commit: WW-4762 Drops unused code

Repository: struts
Updated Branches:
  refs/heads/master 8b862f7cb -> cb0f42abc


WW-4762 Drops unused code


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/45d1a491
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/45d1a491
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/45d1a491

Branch: refs/heads/master
Commit: 45d1a491311a82f6b2404d839e7e18269a26192f
Parents: b46a6e7
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 11:55:49 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 11:55:49 2017 +0200

----------------------------------------------------------------------
 .../org/apache/struts2/util/StrutsTestCaseHelper.java    | 11 -----------
 1 file changed, 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/45d1a491/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java b/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
index 9c449f5..f8994d1 100644
--- a/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
+++ b/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
@@ -23,7 +23,6 @@ package org.apache.struts2.util;
 
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.inject.Container;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
 import com.opensymphony.xwork2.util.ValueStack;
 import com.opensymphony.xwork2.util.ValueStackFactory;
 import org.apache.struts2.dispatcher.Dispatcher;
@@ -40,16 +39,6 @@ import java.util.Map;
  */
 public class StrutsTestCaseHelper {
     
-    /**
-     * Sets up the configuration settings, XWork configuration, and
-     * message resources
-     *
-     * @throws Exception in case of any error
-     */
-    public static void setUp() throws Exception {
-        DefaultLocalizedTextProvider.clearDefaultResourceBundles();
-    }
-    
     public static Dispatcher initDispatcher(ServletContext ctx, Map<String,String> params) {
         if (params == null) {
             params = new HashMap<>();


[08/10] struts git commit: WW-4762 Extracts common method

Posted by lu...@apache.org.
WW-4762 Extracts common method


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/fd168c42
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/fd168c42
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/fd168c42

Branch: refs/heads/master
Commit: fd168c423f70383a6240cba9809a87b661ef39de
Parents: 7360b5e
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed May 10 14:12:39 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed May 10 14:12:39 2017 +0200

----------------------------------------------------------------------
 .../util/AbstractLocalizedTextProvider.java     | 59 ++++++++++++++++++++
 .../util/DefaultLocalizedTextProvider.java      | 48 ----------------
 .../util/StrutsLocalizedTextProvider.java       | 58 -------------------
 3 files changed, 59 insertions(+), 106 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/fd168c42/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
index 1c11fec..fcef47e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -148,6 +148,55 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
         return null;
     }
 
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName, in the specified resource
+     * bundle.
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * <p>
+     * If a message is <b>not</b> found a WARN log will be logged.
+     * </p>
+     *
+     * @param bundle         the bundle
+     * @param aTextName      the key
+     * @param locale         the locale
+     * @param defaultMessage the default message to use if no message was found in the bundle
+     * @param args           arguments for the message formatter.
+     * @param valueStack     the OGNL value stack.
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
+                           ValueStack valueStack) {
+        try {
+            reloadBundles(valueStack.getContext());
+
+            String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
+            MessageFormat mf = buildMessageFormat(message, locale);
+
+            return formatWithNullDetection(mf, args);
+        } catch (MissingResourceException ex) {
+            if (devMode) {
+                LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+            } else {
+                LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+            }
+        }
+
+        GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+        if (unableToFindTextForKey(result)) {
+            LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
+        }
+        return result != null ? result.message : null;
+    }
+
     /**
      * @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader
      */
@@ -320,6 +369,16 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
         }
         return bundle;
     }
+    
+    /**
+     * Clears all the internal lists.
+     *
+     * @deprecated used only in tests
+     */
+    @Deprecated
+    public void reset() {
+        // no-op
+    }
 
     /**
      * Determines if we found the text in the bundles.

http://git-wip-us.apache.org/repos/asf/struts/blob/fd168c42/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
index acd3943..a709180 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
@@ -259,52 +259,4 @@ public class DefaultLocalizedTextProvider extends AbstractLocalizedTextProvider
         return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
     }
 
-    /**
-     * <p>
-     * Finds a localized text message for the given key, aTextName, in the specified resource
-     * bundle.
-     * </p>
-     *
-     * <p>
-     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
-     * will be treated as an OGNL expression and evaluated as such.
-     * </p>
-     *
-     * <p>
-     * If a message is <b>not</b> found a WARN log will be logged.
-     * </p>
-     *
-     * @param bundle         the bundle
-     * @param aTextName      the key
-     * @param locale         the locale
-     * @param defaultMessage the default message to use if no message was found in the bundle
-     * @param args           arguments for the message formatter.
-     * @param valueStack     the OGNL value stack.
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     */
-    @Override
-    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
-                           ValueStack valueStack) {
-        try {
-            reloadBundles(valueStack.getContext());
-
-            String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
-            MessageFormat mf = buildMessageFormat(message, locale);
-
-            return formatWithNullDetection(mf, args);
-        } catch (MissingResourceException ex) {
-            if (devMode) {
-                LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
-            } else {
-                LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
-            }
-        }
-
-        GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
-        if (unableToFindTextForKey(result)) {
-            LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
-        }
-        return result != null ? result.message : null;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/struts/blob/fd168c42/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 0b97279..a31c4ae 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -419,62 +419,4 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
         return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
     }
 
-    /**
-     * <p>
-     * Finds a localized text message for the given key, aTextName, in the specified resource
-     * bundle.
-     * </p>
-     *
-     * <p>
-     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
-     * will be treated as an OGNL expression and evaluated as such.
-     * </p>
-     *
-     * <p>
-     * If a message is <b>not</b> found a WARN log will be logged.
-     * </p>
-     *
-     * @param bundle         the bundle
-     * @param aTextName      the key
-     * @param locale         the locale
-     * @param defaultMessage the default message to use if no message was found in the bundle
-     * @param args           arguments for the message formatter.
-     * @param valueStack     the OGNL value stack.
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     */
-    @Override
-    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
-                           ValueStack valueStack) {
-        try {
-            reloadBundles(valueStack.getContext());
-
-            String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
-            MessageFormat mf = buildMessageFormat(message, locale);
-
-            return formatWithNullDetection(mf, args);
-        } catch (MissingResourceException ex) {
-            if (devMode) {
-                LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
-            } else {
-                LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
-            }
-        }
-
-        GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
-        if (unableToFindTextForKey(result)) {
-            LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
-        }
-        return result != null ? result.message : null;
-    }
-
-    /**
-     * Clears all the internal lists.
-     *
-     * @deprecated used only in tests
-     */
-    @Deprecated
-    public void reset() {
-        // no-op
-    }
-
 }


[10/10] struts git commit: WW-4762 Adds additiona LocalizedTextProvider which uses only default bundles

Posted by lu...@apache.org.
WW-4762 Adds additiona LocalizedTextProvider which uses only default bundles


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/cb0f42ab
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/cb0f42ab
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/cb0f42ab

Branch: refs/heads/master
Commit: cb0f42abccc884b2243fd9b9aac6a63f109e1af4
Parents: 8b862f7 4604333
Author: Lukasz Lenart <lu...@apache.org>
Authored: Thu May 18 10:09:33 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Thu May 18 10:09:33 2017 +0200

----------------------------------------------------------------------
 .../xwork2/DefaultTextProvider.java             |   8 +-
 .../config/impl/DefaultConfiguration.java       |   2 +-
 .../providers/XWorkConfigurationProvider.java   |   4 +-
 .../util/AbstractLocalizedTextProvider.java     | 576 ++++++++++++++
 .../util/DefaultLocalizedTextProvider.java      | 764 +------------------
 .../util/StrutsLocalizedTextProvider.java       | 422 ++++++++++
 .../apache/struts2/dispatcher/Dispatcher.java   |   1 -
 .../struts2/util/StrutsTestCaseHelper.java      |  11 -
 core/src/main/resources/struts-default.xml      |   4 +-
 .../util/DefaultLocalizedTextProviderTest.java  | 267 -------
 .../util/StrutsLocalizedTextProviderTest.java   | 267 +++++++
 .../PropertiesConfigurationProviderTest.java    |   1 -
 12 files changed, 1285 insertions(+), 1042 deletions(-)
----------------------------------------------------------------------



[07/10] struts git commit: WW-4762 Moves common method up

Posted by lu...@apache.org.
WW-4762 Moves common method up


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/7360b5e1
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/7360b5e1
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/7360b5e1

Branch: refs/heads/master
Commit: 7360b5e1956f415866c189d7adda861456d4d069
Parents: bb19a62
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed May 10 13:59:20 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed May 10 13:59:20 2017 +0200

----------------------------------------------------------------------
 .../util/AbstractLocalizedTextProvider.java     | 19 ++++++++++++++++++
 .../util/StrutsLocalizedTextProvider.java       | 21 --------------------
 2 files changed, 19 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/7360b5e1/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
index 8c377c1..1c11fec 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -6,6 +6,7 @@ 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.logging.log4j.message.ParameterizedMessage;
 import org.apache.struts2.StrutsConstants;
 
 import java.lang.reflect.Field;
@@ -20,6 +21,7 @@ import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.StringTokenizer;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -77,6 +79,23 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
         return Thread.currentThread().getContextClassLoader();
     }
 
+    @Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false)
+    public void setCustomI18NResources(String bundles) {
+        if (bundles != null && bundles.length() > 0) {
+            StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
+
+            while (customBundles.hasMoreTokens()) {
+                String name = customBundles.nextToken();
+                try {
+                    LOG.trace("Loading global messages from [{}]", name);
+                    addDefaultResourceBundle(name);
+                } catch (Exception e) {
+                    LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
+                }
+            }
+        }
+    }
+
     /**
      * Returns a localized message for the specified key, aTextName.  Neither the key nor the
      * message is evaluated.

http://git-wip-us.apache.org/repos/asf/struts/blob/7360b5e1/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 9552869..0b97279 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -25,19 +25,15 @@ import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.ActionInvocation;
 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.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.text.MessageFormat;
 import java.util.Locale;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
-import java.util.StringTokenizer;
 
 /**
  * Provides support for localization in the framework, it can be used to read only default bundles,
@@ -62,23 +58,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
         addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
     }
 
-    @Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false)
-    public void setCustomI18NResources(String bundles) {
-        if (bundles != null && bundles.length() > 0) {
-            StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
-
-            while (customBundles.hasMoreTokens()) {
-                String name = customBundles.nextToken();
-                try {
-                    LOG.trace("Loading global messages from [{}]", name);
-                    addDefaultResourceBundle(name);
-                } catch (Exception e) {
-                    LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
-                }
-            }
-        }
-    }
-
     /**
      * 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


[06/10] struts git commit: WW-4762 Introduces default provider which only uses default bundles

Posted by lu...@apache.org.
WW-4762 Introduces default provider which only uses default bundles


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/bb19a620
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/bb19a620
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/bb19a620

Branch: refs/heads/master
Commit: bb19a6209886795b76385e48fd86424c144ee029
Parents: 2e23d7a
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 20:28:11 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 20:28:11 2017 +0200

----------------------------------------------------------------------
 .../util/AbstractLocalizedTextProvider.java     | 145 ++++++++-
 .../util/DefaultLocalizedTextProvider.java      | 310 +++++++++++++++++++
 .../util/StrutsLocalizedTextProvider.java       | 161 +---------
 3 files changed, 461 insertions(+), 155 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/bb19a620/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
index a2578bc..8c377c1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -20,11 +20,12 @@ import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-public abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
+abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
 
     private static final Logger LOG = LogManager.getLogger(AbstractLocalizedTextProvider.class);
 
@@ -302,6 +303,26 @@ public abstract class AbstractLocalizedTextProvider implements LocalizedTextProv
     }
 
     /**
+     * Determines if we found the text in the bundles.
+     *
+     * @param result the result so far
+     * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
+     */
+    protected boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
+        if (result == null || result.message == null) {
+            return true;
+        }
+
+        // did we find it in the bundle, then no problem?
+        if (result.foundInBundle) {
+            return false;
+        }
+
+        // not found in bundle
+        return true;
+    }
+
+    /**
      * 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.
@@ -313,6 +334,128 @@ public abstract class AbstractLocalizedTextProvider implements LocalizedTextProv
         return prefix + aBundleName + "_" + locale.toString();
     }
 
+    /**
+     * @return the default message.
+     */
+    protected GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
+                                                                String defaultMessage) {
+        GetDefaultMessageReturnArg result = null;
+        boolean found = true;
+
+        if (key != null) {
+            String message = findDefaultText(key, locale);
+
+            if (message == null) {
+                message = defaultMessage;
+                found = false; // not found in bundles
+            }
+
+            // defaultMessage may be null
+            if (message != null) {
+                MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
+
+                String msg = formatWithNullDetection(mf, args);
+                result = new GetDefaultMessageReturnArg(msg, found);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * @return the message from the named resource bundle.
+     */
+    protected String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
+        ResourceBundle bundle = findResourceBundle(bundleName, locale);
+        if (bundle == null) {
+            return null;
+        }
+        if (valueStack != null)
+            reloadBundles(valueStack.getContext());
+        try {
+        	String message = bundle.getString(key);
+        	if (valueStack != null)
+        		message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
+            MessageFormat mf = buildMessageFormat(message, locale);
+            return formatWithNullDetection(mf, args);
+        } catch (MissingResourceException e) {
+            if (devMode) {
+                LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
+            } else {
+                LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Traverse up class hierarchy looking for message.  Looks at class, then implemented interface,
+     * before going up hierarchy.
+     *
+     * @return the message
+     */
+    protected String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
+                                      ValueStack valueStack) {
+        if (checked == null) {
+            checked = new TreeSet<>();
+        } else if (checked.contains(clazz.getName())) {
+            return null;
+        }
+
+        // look in properties of this class
+        String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
+
+        if (msg != null) {
+            return msg;
+        }
+
+        if (indexedKey != null) {
+            msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
+
+            if (msg != null) {
+                return msg;
+            }
+        }
+
+        // look in properties of implemented interfaces
+        Class[] interfaces = clazz.getInterfaces();
+
+        for (Class anInterface : interfaces) {
+            msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
+
+            if (msg != null) {
+                return msg;
+            }
+
+            if (indexedKey != null) {
+                msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
+
+                if (msg != null) {
+                    return msg;
+                }
+            }
+        }
+
+        // traverse up hierarchy
+        if (clazz.isInterface()) {
+            interfaces = clazz.getInterfaces();
+
+            for (Class anInterface : interfaces) {
+                msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
+
+                if (msg != null) {
+                    return msg;
+                }
+            }
+        } else {
+            if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
+                return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
+            }
+        }
+
+        return null;
+    }
+
     static class MessageFormatKey {
         String pattern;
         Locale locale;

http://git-wip-us.apache.org/repos/asf/struts/blob/bb19a620/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
new file mode 100644
index 0000000..acd3943
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
@@ -0,0 +1,310 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ModelDriven;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * 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 DefaultLocalizedTextProvider extends AbstractLocalizedTextProvider {
+
+    private static final Logger LOG = LogManager.getLogger(DefaultLocalizedTextProvider.class);
+
+    public DefaultLocalizedTextProvider() {
+        addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE);
+        addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
+    }
+
+    /**
+     * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
+     * with aTextName as the default message.
+     *
+     * @param aClass    class name
+     * @param aTextName text name
+     * @param locale    the locale
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
+     */
+    @Override
+    public String findText(Class aClass, String aTextName, Locale locale) {
+        return findText(aClass, aTextName, locale, aTextName, new Object[0]);
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName. Both the key and the message
+     * itself is evaluated as required.  The following algorithm is used to find the requested
+     * message:
+     * </p>
+     *
+     * <ol>
+     * <li>Look for message in aClass' class hierarchy.
+     * <ol>
+     * <li>Look for the message in a resource bundle for aClass</li>
+     * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+     * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+     * </ol></li>
+     * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+     * the model's class hierarchy (repeat sub-steps listed above).</li>
+     * <li>If not found, look for message in child property.  This is determined by evaluating
+     * the message key as an OGNL expression.  For example, if the key is
+     * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+     * object.  If so, repeat the entire process fromthe beginning with the object's class as
+     * aClass and "address.state" as the message key.</li>
+     * <li>If not found, look for the message in aClass' package hierarchy.</li>
+     * <li>If still not found, look for the message in the default resource bundles.</li>
+     * <li>Return defaultMessage</li>
+     * </ol>
+     *
+     * <p>
+     * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+     * message for that specific key cannot be found, the general form will also be looked up
+     * (i.e. user.phone[*]).
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * @param aClass         the class whose name to use as the start point for the search
+     * @param aTextName      the key to find the text message for
+     * @param locale         the locale the message should be for
+     * @param defaultMessage the message to be returned if no text message can be found in any
+     *                       resource bundle
+     * @param args           arguments
+     *                       resource bundle
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+        ValueStack valueStack = ActionContext.getContext().getValueStack();
+        return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
+
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName. Both the key and the message
+     * itself is evaluated as required.  The following algorithm is used to find the requested
+     * message:
+     * </p>
+     *
+     * <ol>
+     * <li>Look for message in aClass' class hierarchy.
+     * <ol>
+     * <li>Look for the message in a resource bundle for aClass</li>
+     * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+     * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+     * </ol></li>
+     * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+     * the model's class hierarchy (repeat sub-steps listed above).</li>
+     * <li>If not found, look for message in child property.  This is determined by evaluating
+     * the message key as an OGNL expression.  For example, if the key is
+     * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+     * object.  If so, repeat the entire process fromthe beginning with the object's class as
+     * aClass and "address.state" as the message key.</li>
+     * <li>If not found, look for the message in aClass' package hierarchy.</li>
+     * <li>If still not found, look for the message in the default resource bundles.</li>
+     * <li>Return defaultMessage</li>
+     * </ol>
+     *
+     * <p>
+     * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+     * message for that specific key cannot be found, the general form will also be looked up
+     * (i.e. user.phone[*]).
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * <p>
+     * If a message is <b>not</b> found a WARN log will be logged.
+     * </p>
+     *
+     * @param aClass         the class whose name to use as the start point for the search
+     * @param aTextName      the key to find the text message for
+     * @param locale         the locale the message should be for
+     * @param defaultMessage the message to be returned if no text message can be found in any
+     *                       resource bundle
+     * @param args           arguments
+     * @param valueStack     the value stack to use to evaluate expressions instead of the
+     *                       one in the ActionContext ThreadLocal
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) {
+        String indexedTextName = null;
+        if (aTextName == null) {
+            LOG.warn("Trying to find text with null key!");
+            aTextName = "";
+        }
+        // calculate indexedTextName (collection[*]) if applicable
+        if (aTextName.contains("[")) {
+            int i = -1;
+
+            indexedTextName = aTextName;
+
+            while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
+                int j = indexedTextName.indexOf("]", i);
+                String a = indexedTextName.substring(0, i);
+                String b = indexedTextName.substring(j);
+                indexedTextName = a + "[*" + b;
+            }
+        }
+
+        // get default
+        GetDefaultMessageReturnArg result;
+        if (indexedTextName == null) {
+            result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+        } else {
+            result = getDefaultMessage(aTextName, locale, valueStack, args, null);
+            if (result != null && result.message != null) {
+                return result.message;
+            }
+            result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
+        }
+
+        // could we find the text, if not log a warn
+        if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
+            String warn = "Unable to find text for key '" + aTextName + "' ";
+            if (indexedTextName != null) {
+                warn += " or indexed key '" + indexedTextName + "' ";
+            }
+            warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
+            LOG.debug(warn);
+        }
+
+        return result != null ? result.message : null;
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName, in the specified resource bundle
+     * with aTextName as the default message.
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * @param bundle    a resource bundle name
+     * @param aTextName text name
+     * @param locale    the locale
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     * @see #findText(ResourceBundle, String, Locale, String, Object[])
+     */
+    @Override
+    public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
+        return findText(bundle, aTextName, locale, aTextName, new Object[0]);
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName, in the specified resource
+     * bundle.
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * <p>
+     * If a message is <b>not</b> found a WARN log will be logged.
+     * </p>
+     *
+     * @param bundle         the bundle
+     * @param aTextName      the key
+     * @param locale         the locale
+     * @param defaultMessage the default message to use if no message was found in the bundle
+     * @param args           arguments for the message formatter.
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+        ValueStack valueStack = ActionContext.getContext().getValueStack();
+        return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName, in the specified resource
+     * bundle.
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * <p>
+     * If a message is <b>not</b> found a WARN log will be logged.
+     * </p>
+     *
+     * @param bundle         the bundle
+     * @param aTextName      the key
+     * @param locale         the locale
+     * @param defaultMessage the default message to use if no message was found in the bundle
+     * @param args           arguments for the message formatter.
+     * @param valueStack     the OGNL value stack.
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
+                           ValueStack valueStack) {
+        try {
+            reloadBundles(valueStack.getContext());
+
+            String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
+            MessageFormat mf = buildMessageFormat(message, locale);
+
+            return formatWithNullDetection(mf, args);
+        } catch (MissingResourceException ex) {
+            if (devMode) {
+                LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+            } else {
+                LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+            }
+        }
+
+        GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+        if (unableToFindTextForKey(result)) {
+            LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
+        }
+        return result != null ? result.message : null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/bb19a620/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 3d03f9a..9552869 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -37,9 +37,7 @@ import java.text.MessageFormat;
 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,
@@ -89,7 +87,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
      * @param localeStr     The locale String to parse.
      * @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
      * @return requested Locale
-     *
      * @deprecated please use {@link org.apache.commons.lang3.LocaleUtils#toLocale(String)}
      */
     @Deprecated
@@ -130,10 +127,9 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
      * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
      * with aTextName as the default message.
      *
-     * @param aClass class name
-     * @param aTextName  text name
-     * @param locale the locale
-     *
+     * @param aClass    class name
+     * @param aTextName text name
+     * @param locale    the locale
      * @return the localized text, or null if none can be found and no defaultMessage is provided
      * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
      */
@@ -251,7 +247,7 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
                            ValueStack valueStack) {
         String indexedTextName = null;
         if (aTextName == null) {
-        	LOG.warn("Trying to find text with null key!");
+            LOG.warn("Trying to find text with null key!");
             aTextName = "";
         }
         // calculate indexedTextName (collection[*]) if applicable
@@ -395,26 +391,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
     }
 
     /**
-     * Determines if we found the text in the bundles.
-     *
-     * @param result the result so far
-     * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
-     */
-    private boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
-        if (result == null || result.message == null) {
-            return true;
-        }
-
-        // did we find it in the bundle, then no problem?
-        if (result.foundInBundle) {
-            return false;
-        }
-
-        // not found in bundle
-        return true;
-    }
-
-    /**
      * <p>
      * Finds a localized text message for the given key, aTextName, in the specified resource bundle
      * with aTextName as the default message.
@@ -425,10 +401,9 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
      * will be treated as an OGNL expression and evaluated as such.
      * </p>
      *
-     * @param bundle a resource bundle name
-     * @param aTextName  text name
-     * @param locale the locale
-     *
+     * @param bundle    a resource bundle name
+     * @param aTextName text name
+     * @param locale    the locale
      * @return the localized text, or null if none can be found and no defaultMessage is provided
      * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
      */
@@ -514,128 +489,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
     }
 
     /**
-     * @return the default message.
-     */
-    private GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
-                                                                String defaultMessage) {
-        GetDefaultMessageReturnArg result = null;
-        boolean found = true;
-
-        if (key != null) {
-            String message = findDefaultText(key, locale);
-
-            if (message == null) {
-                message = defaultMessage;
-                found = false; // not found in bundles
-            }
-
-            // defaultMessage may be null
-            if (message != null) {
-                MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
-
-                String msg = formatWithNullDetection(mf, args);
-                result = new GetDefaultMessageReturnArg(msg, found);
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * @return the message from the named resource bundle.
-     */
-    private String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
-        ResourceBundle bundle = findResourceBundle(bundleName, locale);
-        if (bundle == null) {
-            return null;
-        }
-        if (valueStack != null) 
-            reloadBundles(valueStack.getContext());
-        try {
-        	String message = bundle.getString(key);
-        	if (valueStack != null) 
-        		message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
-            MessageFormat mf = buildMessageFormat(message, locale);
-            return formatWithNullDetection(mf, args);
-        } catch (MissingResourceException e) {
-            if (devMode) {
-                LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
-            } else {
-                LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Traverse up class hierarchy looking for message.  Looks at class, then implemented interface,
-     * before going up hierarchy.
-     *
-     * @return the message
-     */
-    private String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
-                                      ValueStack valueStack) {
-        if (checked == null) {
-            checked = new TreeSet<>();
-        } else if (checked.contains(clazz.getName())) {
-            return null;
-        }
-
-        // look in properties of this class
-        String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
-
-        if (msg != null) {
-            return msg;
-        }
-
-        if (indexedKey != null) {
-            msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
-
-            if (msg != null) {
-                return msg;
-            }
-        }
-
-        // look in properties of implemented interfaces
-        Class[] interfaces = clazz.getInterfaces();
-
-        for (Class anInterface : interfaces) {
-            msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
-
-            if (msg != null) {
-                return msg;
-            }
-
-            if (indexedKey != null) {
-                msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
-
-                if (msg != null) {
-                    return msg;
-                }
-            }
-        }
-
-        // traverse up hierarchy
-        if (clazz.isInterface()) {
-            interfaces = clazz.getInterfaces();
-
-            for (Class anInterface : interfaces) {
-                msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
-
-                if (msg != null) {
-                    return msg;
-                }
-            }
-        } else {
-            if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
-                return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
-            }
-        }
-
-        return null;
-    }
-
-    /**
      * Clears all the internal lists.
      *
      * @deprecated used only in tests


[03/10] struts git commit: WW-4762 Uses Struts prefix for the main implementation

Posted by lu...@apache.org.
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java
deleted file mode 100644
index a9dca08..0000000
--- a/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright 2002-2006,2009 The Apache Software Foundation.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * 
- *      http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.opensymphony.xwork2.util;
-
-import com.mockobjects.dynamic.Mock;
-import com.opensymphony.xwork2.*;
-import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
-import com.opensymphony.xwork2.test.ModelDrivenAction2;
-import com.opensymphony.xwork2.test.TestBean2;
-
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.ResourceBundle;
-
-
-/**
- * Unit test for {@link DefaultLocalizedTextProvider}.
- *
- * @author jcarreira
- * @author tm_jee
- * 
- * @version $Date$ $Id$
- */
-public class DefaultLocalizedTextProviderTest extends XWorkTestCase {
-
-    private LocalizedTextProvider localizedTextProvider;
-    
-	public void testNpeWhenClassIsPrimitive() throws Exception {
-		ValueStack stack = ActionContext.getContext().getValueStack();
-		stack.push(new MyObject());
-		String result = localizedTextProvider.findText(MyObject.class, "someObj.someI18nKey", Locale.ENGLISH, "default message", null, stack);
-		System.out.println(result);
-	}
-	
-	public static class MyObject extends ActionSupport {
-		public boolean getSomeObj() {
-			return true;
-		}
-	}
-	
-	public void testActionGetTextWithNullObject() throws Exception {
-		MyAction action = new MyAction();
-        container.inject(action);
-		
-		Mock mockActionInvocation = new Mock(ActionInvocation.class);
-        mockActionInvocation.expectAndReturn("getAction", action);
-        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-		ActionContext.getContext().getValueStack().push(action);
-		
-		String message = action.getText("barObj.title");
-		assertEquals("Title:", message);
-	}
-	
-	
-	public static class MyAction extends ActionSupport {
-		private Bar testBean2;
-		
-		public Bar getBarObj() {
-			return testBean2;
-		}
-		public void setBarObj(Bar testBean2) {
-			this.testBean2 = testBean2;
-		}
-	}
-	
-    public void testActionGetText() throws Exception {
-        ModelDrivenAction2 action = new ModelDrivenAction2();
-        container.inject(action);
-
-        TestBean2 bean = (TestBean2) action.getModel();
-        Bar bar = new Bar();
-        bean.setBarObj(bar);
-
-        Mock mockActionInvocation = new Mock(ActionInvocation.class);
-        mockActionInvocation.expectAndReturn("getAction", action);
-        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-        ActionContext.getContext().getValueStack().push(action);
-        ActionContext.getContext().getValueStack().push(action.getModel());
-
-        String message = action.getText("barObj.title");
-        assertEquals("Title:", message);
-    }
-
-    public void testNullKeys() {
-        localizedTextProvider.findText(this.getClass(), null, Locale.getDefault());
-    }
-
-    public void testActionGetTextXXX() throws Exception {
-        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/FindMe");
-
-        SimpleAction action = new SimpleAction();
-        container.inject(action);
-
-        Mock mockActionInvocation = new Mock(ActionInvocation.class);
-        mockActionInvocation.expectAndReturn("getAction", action);
-        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-        ActionContext.getContext().getValueStack().push(action);
-
-        String message = action.getText("bean.name");
-        String foundBean2 = action.getText("bean2.name");
-
-        assertEquals("Okay! You found Me!", foundBean2);
-        assertEquals("Haha you cant FindMe!", message);
-    }
-
-    public void testAddDefaultResourceBundle() {
-        String text = localizedTextProvider.findDefaultText("foo.range", Locale.getDefault());
-        assertNull("Found message when it should not be available.", text);
-
-        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
-
-        String message = localizedTextProvider.findDefaultText("foo.range", Locale.US);
-        assertEquals("Foo Range Message", message);
-    }
-
-    public void testAddDefaultResourceBundle2() throws Exception {
-        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
-
-        ActionProxy proxy = actionProxyFactory.createActionProxy("/", "packagelessAction", null, new HashMap<String, Object>(), false, true);
-        proxy.execute();
-    }
-
-    public void testDefaultMessage() throws Exception {
-        String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
-        assertEquals("Error during Action invocation", message);
-    }
-
-    public void testDefaultMessageOverride() throws Exception {
-        String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
-        assertEquals("Error during Action invocation", message);
-
-        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/test");
-
-        message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
-        assertEquals("Testing resource bundle override", message);
-    }
-
-    public void testFindTextInChildProperty() throws Exception {
-        ModelDriven action = new ModelDrivenAction2();
-        TestBean2 bean = (TestBean2) action.getModel();
-        Bar bar = new Bar();
-        bean.setBarObj(bar);
-
-        Mock mockActionInvocation = new Mock(ActionInvocation.class);
-        mockActionInvocation.expectAndReturn("hashCode", 0);
-        mockActionInvocation.expectAndReturn("getAction", action);
-        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-        ActionContext.getContext().getValueStack().push(action);
-        ActionContext.getContext().getValueStack().push(action.getModel());
-
-        String message = localizedTextProvider.findText(ModelDrivenAction2.class, "invalid.fieldvalue.barObj.title", Locale.getDefault());
-        assertEquals("Title is invalid!", message);
-    }
-
-    public void testFindTextInInterface() throws Exception {
-        Action action = new ModelDrivenAction2();
-        Mock mockActionInvocation = new Mock(ActionInvocation.class);
-        mockActionInvocation.expectAndReturn("getAction", action);
-        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-
-        String message = localizedTextProvider.findText(ModelDrivenAction2.class, "test.foo", Locale.getDefault());
-        assertEquals("Foo!", message);
-    }
-
-    public void testFindTextInPackage() throws Exception {
-        ModelDriven action = new ModelDrivenAction2();
-
-        Mock mockActionInvocation = new Mock(ActionInvocation.class);
-        mockActionInvocation.expectAndReturn("getAction", action);
-        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-
-        String message = localizedTextProvider.findText(ModelDrivenAction2.class, "package.properties", Locale.getDefault());
-        assertEquals("It works!", message);
-    }
-
-    public void testParameterizedDefaultMessage() throws Exception {
-        String message = localizedTextProvider.findDefaultText("xwork.exception.missing-action", Locale.getDefault(), new String[]{"AddUser"});
-        assertEquals("There is no Action mapped for action name AddUser.", message);
-    }
-
-    public void testParameterizedDefaultMessageWithPackage() throws Exception {
-        String message = localizedTextProvider.findDefaultText("xwork.exception.missing-package-action", Locale.getDefault(), new String[]{"blah", "AddUser"});
-        assertEquals("There is no Action mapped for namespace blah and action name AddUser.", message);
-    }
-
-    public void testLocalizedDateFormatIsUsed() throws ParseException {
-        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
-        Date date = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse("01/01/2015");
-        Object[] params = new Object[]{ date };
-        String usDate = localizedTextProvider.findDefaultText("test.format.date", Locale.US, params);
-        String germanDate = localizedTextProvider.findDefaultText("test.format.date", Locale.GERMANY, params);
-        assertEquals(usDate, "1/1/15");
-        assertEquals(germanDate, "01.01.15");
-    }
-
-    public void testXW377() {
-        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
-
-        String text = localizedTextProvider.findText(Bar.class, "xw377", ActionContext.getContext().getLocale(), "xw377", null, ActionContext.getContext().getValueStack());
-        assertEquals("xw377", text); // should not log
-
-        String text2 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "notinbundle", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
-        assertEquals("hello", text2); // should log WARN
-
-        String text3 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "notinbundle.key", ActionContext.getContext().getLocale(), "notinbundle.key", null, ActionContext.getContext().getValueStack());
-        assertEquals("notinbundle.key", text3); // should log WARN
-
-        String text4 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "xw377", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
-        assertEquals("xw377", text4); // should not log
-
-        String text5 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "username", ActionContext.getContext().getLocale(), null, null, ActionContext.getContext().getValueStack());
-        assertEquals("Santa", text5); // should not log
-    }
-
-    public void testXW404() {
-        // This tests will try to load bundles from the 3 locales but we only have files for France and Germany.
-        // Before this fix loading the bundle for Germany failed since Italy have previously failed and thus the misses cache
-        // contained a false entry
-
-        ResourceBundle rbFrance = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.FRANCE);
-        ResourceBundle rbItaly = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.ITALY);
-        ResourceBundle rbGermany = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.GERMANY);
-
-        assertNotNull(rbFrance);
-        assertEquals("Bonjour", rbFrance.getString("hello"));
-
-        assertNull(rbItaly);
-
-        assertNotNull(rbGermany);
-        assertEquals("Hallo", rbGermany.getString("hello"));
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        XmlConfigurationProvider provider = new XmlConfigurationProvider("xwork-sample.xml");
-        container.inject(provider);
-        loadConfigurationProviders(provider);
-
-        localizedTextProvider = container.getInstance(LocalizedTextProvider.class);
-        
-        ActionContext.getContext().setLocale(Locale.US);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        localizedTextProvider = null;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
new file mode 100644
index 0000000..237db80
--- /dev/null
+++ b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util;
+
+import com.mockobjects.dynamic.Mock;
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
+import com.opensymphony.xwork2.test.ModelDrivenAction2;
+import com.opensymphony.xwork2.test.TestBean2;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+
+/**
+ * Unit test for {@link StrutsLocalizedTextProvider}.
+ *
+ * @author jcarreira
+ * @author tm_jee
+ * 
+ * @version $Date$ $Id$
+ */
+public class StrutsLocalizedTextProviderTest extends XWorkTestCase {
+
+    private LocalizedTextProvider localizedTextProvider;
+    
+	public void testNpeWhenClassIsPrimitive() throws Exception {
+		ValueStack stack = ActionContext.getContext().getValueStack();
+		stack.push(new MyObject());
+		String result = localizedTextProvider.findText(MyObject.class, "someObj.someI18nKey", Locale.ENGLISH, "default message", null, stack);
+		System.out.println(result);
+	}
+	
+	public static class MyObject extends ActionSupport {
+		public boolean getSomeObj() {
+			return true;
+		}
+	}
+	
+	public void testActionGetTextWithNullObject() throws Exception {
+		MyAction action = new MyAction();
+        container.inject(action);
+		
+		Mock mockActionInvocation = new Mock(ActionInvocation.class);
+        mockActionInvocation.expectAndReturn("getAction", action);
+        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+		ActionContext.getContext().getValueStack().push(action);
+		
+		String message = action.getText("barObj.title");
+		assertEquals("Title:", message);
+	}
+	
+	
+	public static class MyAction extends ActionSupport {
+		private Bar testBean2;
+		
+		public Bar getBarObj() {
+			return testBean2;
+		}
+		public void setBarObj(Bar testBean2) {
+			this.testBean2 = testBean2;
+		}
+	}
+	
+    public void testActionGetText() throws Exception {
+        ModelDrivenAction2 action = new ModelDrivenAction2();
+        container.inject(action);
+
+        TestBean2 bean = (TestBean2) action.getModel();
+        Bar bar = new Bar();
+        bean.setBarObj(bar);
+
+        Mock mockActionInvocation = new Mock(ActionInvocation.class);
+        mockActionInvocation.expectAndReturn("getAction", action);
+        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+        ActionContext.getContext().getValueStack().push(action);
+        ActionContext.getContext().getValueStack().push(action.getModel());
+
+        String message = action.getText("barObj.title");
+        assertEquals("Title:", message);
+    }
+
+    public void testNullKeys() {
+        localizedTextProvider.findText(this.getClass(), null, Locale.getDefault());
+    }
+
+    public void testActionGetTextXXX() throws Exception {
+        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/FindMe");
+
+        SimpleAction action = new SimpleAction();
+        container.inject(action);
+
+        Mock mockActionInvocation = new Mock(ActionInvocation.class);
+        mockActionInvocation.expectAndReturn("getAction", action);
+        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+        ActionContext.getContext().getValueStack().push(action);
+
+        String message = action.getText("bean.name");
+        String foundBean2 = action.getText("bean2.name");
+
+        assertEquals("Okay! You found Me!", foundBean2);
+        assertEquals("Haha you cant FindMe!", message);
+    }
+
+    public void testAddDefaultResourceBundle() {
+        String text = localizedTextProvider.findDefaultText("foo.range", Locale.getDefault());
+        assertNull("Found message when it should not be available.", text);
+
+        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
+
+        String message = localizedTextProvider.findDefaultText("foo.range", Locale.US);
+        assertEquals("Foo Range Message", message);
+    }
+
+    public void testAddDefaultResourceBundle2() throws Exception {
+        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
+
+        ActionProxy proxy = actionProxyFactory.createActionProxy("/", "packagelessAction", null, new HashMap<String, Object>(), false, true);
+        proxy.execute();
+    }
+
+    public void testDefaultMessage() throws Exception {
+        String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
+        assertEquals("Error during Action invocation", message);
+    }
+
+    public void testDefaultMessageOverride() throws Exception {
+        String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
+        assertEquals("Error during Action invocation", message);
+
+        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/test");
+
+        message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
+        assertEquals("Testing resource bundle override", message);
+    }
+
+    public void testFindTextInChildProperty() throws Exception {
+        ModelDriven action = new ModelDrivenAction2();
+        TestBean2 bean = (TestBean2) action.getModel();
+        Bar bar = new Bar();
+        bean.setBarObj(bar);
+
+        Mock mockActionInvocation = new Mock(ActionInvocation.class);
+        mockActionInvocation.expectAndReturn("hashCode", 0);
+        mockActionInvocation.expectAndReturn("getAction", action);
+        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+        ActionContext.getContext().getValueStack().push(action);
+        ActionContext.getContext().getValueStack().push(action.getModel());
+
+        String message = localizedTextProvider.findText(ModelDrivenAction2.class, "invalid.fieldvalue.barObj.title", Locale.getDefault());
+        assertEquals("Title is invalid!", message);
+    }
+
+    public void testFindTextInInterface() throws Exception {
+        Action action = new ModelDrivenAction2();
+        Mock mockActionInvocation = new Mock(ActionInvocation.class);
+        mockActionInvocation.expectAndReturn("getAction", action);
+        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+
+        String message = localizedTextProvider.findText(ModelDrivenAction2.class, "test.foo", Locale.getDefault());
+        assertEquals("Foo!", message);
+    }
+
+    public void testFindTextInPackage() throws Exception {
+        ModelDriven action = new ModelDrivenAction2();
+
+        Mock mockActionInvocation = new Mock(ActionInvocation.class);
+        mockActionInvocation.expectAndReturn("getAction", action);
+        ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+
+        String message = localizedTextProvider.findText(ModelDrivenAction2.class, "package.properties", Locale.getDefault());
+        assertEquals("It works!", message);
+    }
+
+    public void testParameterizedDefaultMessage() throws Exception {
+        String message = localizedTextProvider.findDefaultText("xwork.exception.missing-action", Locale.getDefault(), new String[]{"AddUser"});
+        assertEquals("There is no Action mapped for action name AddUser.", message);
+    }
+
+    public void testParameterizedDefaultMessageWithPackage() throws Exception {
+        String message = localizedTextProvider.findDefaultText("xwork.exception.missing-package-action", Locale.getDefault(), new String[]{"blah", "AddUser"});
+        assertEquals("There is no Action mapped for namespace blah and action name AddUser.", message);
+    }
+
+    public void testLocalizedDateFormatIsUsed() throws ParseException {
+        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
+        Date date = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse("01/01/2015");
+        Object[] params = new Object[]{ date };
+        String usDate = localizedTextProvider.findDefaultText("test.format.date", Locale.US, params);
+        String germanDate = localizedTextProvider.findDefaultText("test.format.date", Locale.GERMANY, params);
+        assertEquals(usDate, "1/1/15");
+        assertEquals(germanDate, "01.01.15");
+    }
+
+    public void testXW377() {
+        localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
+
+        String text = localizedTextProvider.findText(Bar.class, "xw377", ActionContext.getContext().getLocale(), "xw377", null, ActionContext.getContext().getValueStack());
+        assertEquals("xw377", text); // should not log
+
+        String text2 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "notinbundle", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
+        assertEquals("hello", text2); // should log WARN
+
+        String text3 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "notinbundle.key", ActionContext.getContext().getLocale(), "notinbundle.key", null, ActionContext.getContext().getValueStack());
+        assertEquals("notinbundle.key", text3); // should log WARN
+
+        String text4 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "xw377", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
+        assertEquals("xw377", text4); // should not log
+
+        String text5 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "username", ActionContext.getContext().getLocale(), null, null, ActionContext.getContext().getValueStack());
+        assertEquals("Santa", text5); // should not log
+    }
+
+    public void testXW404() {
+        // This tests will try to load bundles from the 3 locales but we only have files for France and Germany.
+        // Before this fix loading the bundle for Germany failed since Italy have previously failed and thus the misses cache
+        // contained a false entry
+
+        ResourceBundle rbFrance = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.FRANCE);
+        ResourceBundle rbItaly = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.ITALY);
+        ResourceBundle rbGermany = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.GERMANY);
+
+        assertNotNull(rbFrance);
+        assertEquals("Bonjour", rbFrance.getString("hello"));
+
+        assertNull(rbItaly);
+
+        assertNotNull(rbGermany);
+        assertEquals("Hallo", rbGermany.getString("hello"));
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        XmlConfigurationProvider provider = new XmlConfigurationProvider("xwork-sample.xml");
+        container.inject(provider);
+        loadConfigurationProviders(provider);
+
+        localizedTextProvider = container.getInstance(LocalizedTextProvider.class);
+        
+        ActionContext.getContext().setLocale(Locale.US);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        localizedTextProvider = null;
+    }
+
+}


[09/10] struts git commit: WW-4762 Defines default-only bean

Posted by lu...@apache.org.
WW-4762 Defines default-only bean


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/46043336
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/46043336
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/46043336

Branch: refs/heads/master
Commit: 4604333649a0060421c07cd5520c8285ca0c135f
Parents: fd168c4
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed May 10 15:48:35 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed May 10 15:48:35 2017 +0200

----------------------------------------------------------------------
 core/src/main/resources/struts-default.xml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/46043336/core/src/main/resources/struts-default.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index ead2954..bf2c1f7 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -130,8 +130,10 @@
     <bean type="com.opensymphony.xwork2.conversion.impl.NumberConverter" name="struts" class="com.opensymphony.xwork2.conversion.impl.NumberConverter" scope="singleton"/>
     <bean type="com.opensymphony.xwork2.conversion.impl.StringConverter" name="struts" class="com.opensymphony.xwork2.conversion.impl.StringConverter" scope="singleton"/>
 
-    <bean type="com.opensymphony.xwork2.TextProvider" name="system" class="com.opensymphony.xwork2.DefaultTextProvider" scope="singleton" />
+    <bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="only-default" class="com.opensymphony.xwork2.util.DefaultLocalizedTextProvider" scope="singleton" />
     <bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="struts" class="com.opensymphony.xwork2.util.StrutsLocalizedTextProvider" scope="singleton" />
+
+    <bean type="com.opensymphony.xwork2.TextProvider" name="system" class="com.opensymphony.xwork2.DefaultTextProvider" scope="singleton" />
     <bean type="com.opensymphony.xwork2.TextProviderFactory" name="struts" class="com.opensymphony.xwork2.StrutsTextProviderFactory" scope="singleton" />
     <bean type="com.opensymphony.xwork2.LocaleProviderFactory" name="struts" class="com.opensymphony.xwork2.DefaultLocaleProviderFactory" scope="singleton" />
 


[04/10] struts git commit: WW-4762 Uses Struts prefix for the main implementation

Posted by lu...@apache.org.
WW-4762 Uses Struts prefix for the main implementation


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/8bf77a1d
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/8bf77a1d
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/8bf77a1d

Branch: refs/heads/master
Commit: 8bf77a1d19d31dbc8574dcbf958c3da2a4724c84
Parents: 3ffc9b8
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 11:56:48 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 11:56:48 2017 +0200

----------------------------------------------------------------------
 .../config/impl/DefaultConfiguration.java       |    2 +-
 .../providers/XWorkConfigurationProvider.java   |    4 +-
 .../util/DefaultLocalizedTextProvider.java      | 1000 ------------------
 .../util/StrutsLocalizedTextProvider.java       | 1000 ++++++++++++++++++
 core/src/main/resources/struts-default.xml      |    2 +-
 .../util/DefaultLocalizedTextProviderTest.java  |  267 -----
 .../util/StrutsLocalizedTextProviderTest.java   |  267 +++++
 7 files changed, 1271 insertions(+), 1271 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
index 8882a2f..a1b6490 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
@@ -261,7 +261,7 @@ public class DefaultConfiguration implements Configuration {
 
         builder.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON);
 
-        builder.factory(LocalizedTextProvider.class, DefaultLocalizedTextProvider.class, Scope.SINGLETON);
+        builder.factory(LocalizedTextProvider.class, StrutsLocalizedTextProvider.class, Scope.SINGLETON);
         builder.factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON);
         builder.factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON);
 

http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
index 1bc6986..e5692a1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
@@ -72,7 +72,7 @@ import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
 import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
 import com.opensymphony.xwork2.util.CompoundRoot;
 import com.opensymphony.xwork2.LocalizedTextProvider;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
+import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
 import com.opensymphony.xwork2.util.OgnlTextParser;
 import com.opensymphony.xwork2.util.PatternMatcher;
 import com.opensymphony.xwork2.util.TextParser;
@@ -182,7 +182,7 @@ public class XWorkConfigurationProvider implements ConfigurationProvider {
                 .factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON)
 
                 .factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON)
-                .factory(LocalizedTextProvider.class, DefaultLocalizedTextProvider.class, Scope.SINGLETON)
+                .factory(LocalizedTextProvider.class, StrutsLocalizedTextProvider.class, Scope.SINGLETON)
                 .factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON)
                 .factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON)
 

http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
deleted file mode 100644
index 21f7477..0000000
--- a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
+++ /dev/null
@@ -1,1000 +0,0 @@
-/*
- * $Id$
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-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;
-
-/**
- * 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 DefaultLocalizedTextProvider implements LocalizedTextProvider {
-
-    private static final Logger LOG = LogManager.getLogger(DefaultLocalizedTextProvider.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.
-     *
-     * @deprecated used only in tests
-     */
-    @Deprecated
-    public static void clearDefaultResourceBundles() {
-        // no-op
-    }
-
-    public DefaultLocalizedTextProvider() {
-
-        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) {
-            StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
-
-            while (customBundles.hasMoreTokens()) {
-                String name = customBundles.nextToken();
-                try {
-                    LOG.trace("Loading global messages from [{}]", name);
-                    addDefaultResourceBundle(name);
-                } catch (Exception e) {
-                    LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
-                }
-            }
-        }
-    }
-
-    /**
-     * 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()}.
-     *
-     * @param localeStr     The locale String to parse.
-     * @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
-     * @return requested Locale
-     *
-     * @deprecated please use {@link org.apache.commons.lang3.LocaleUtils#toLocale(String)}
-     */
-    @Deprecated
-    public static Locale localeFromString(String localeStr, Locale defaultLocale) {
-        if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) {
-            if (defaultLocale != null) {
-                return defaultLocale;
-            }
-            return Locale.getDefault();
-        }
-
-        int index = localeStr.indexOf('_');
-        if (index < 0) {
-            return new Locale(localeStr);
-        }
-
-        String language = localeStr.substring(0, index);
-        if (index == localeStr.length()) {
-            return new Locale(language);
-        }
-
-        localeStr = localeStr.substring(index + 1);
-        index = localeStr.indexOf('_');
-        if (index < 0) {
-            return new Locale(language, localeStr);
-        }
-
-        String country = localeStr.substring(0, index);
-        if (index == localeStr.length()) {
-            return new Locale(language, country);
-        }
-
-        localeStr = localeStr.substring(index + 1);
-        return new Locale(language, country, localeStr);
-    }
-
-    /**
-     * 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.
-     *
-     * @param aClass class name
-     * @param aTextName  text name
-     * @param locale the locale
-     *
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
-     */
-    @Override
-    public String findText(Class aClass, String aTextName, Locale locale) {
-        return findText(aClass, aTextName, locale, aTextName, new Object[0]);
-    }
-
-    /**
-     * <p>
-     * Finds a localized text message for the given key, aTextName. Both the key and the message
-     * itself is evaluated as required.  The following algorithm is used to find the requested
-     * message:
-     * </p>
-     *
-     * <ol>
-     * <li>Look for message in aClass' class hierarchy.
-     * <ol>
-     * <li>Look for the message in a resource bundle for aClass</li>
-     * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
-     * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
-     * </ol></li>
-     * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
-     * the model's class hierarchy (repeat sub-steps listed above).</li>
-     * <li>If not found, look for message in child property.  This is determined by evaluating
-     * the message key as an OGNL expression.  For example, if the key is
-     * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
-     * object.  If so, repeat the entire process fromthe beginning with the object's class as
-     * aClass and "address.state" as the message key.</li>
-     * <li>If not found, look for the message in aClass' package hierarchy.</li>
-     * <li>If still not found, look for the message in the default resource bundles.</li>
-     * <li>Return defaultMessage</li>
-     * </ol>
-     *
-     * <p>
-     * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
-     * message for that specific key cannot be found, the general form will also be looked up
-     * (i.e. user.phone[*]).
-     * </p>
-     *
-     * <p>
-     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
-     * will be treated as an OGNL expression and evaluated as such.
-     * </p>
-     *
-     * @param aClass         the class whose name to use as the start point for the search
-     * @param aTextName      the key to find the text message for
-     * @param locale         the locale the message should be for
-     * @param defaultMessage the message to be returned if no text message can be found in any
-     *                       resource bundle
-     * @param args           arguments
-     *                       resource bundle
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     */
-    @Override
-    public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
-        ValueStack valueStack = ActionContext.getContext().getValueStack();
-        return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
-
-    }
-
-    /**
-     * <p>
-     * Finds a localized text message for the given key, aTextName. Both the key and the message
-     * itself is evaluated as required.  The following algorithm is used to find the requested
-     * message:
-     * </p>
-     *
-     * <ol>
-     * <li>Look for message in aClass' class hierarchy.
-     * <ol>
-     * <li>Look for the message in a resource bundle for aClass</li>
-     * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
-     * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
-     * </ol></li>
-     * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
-     * the model's class hierarchy (repeat sub-steps listed above).</li>
-     * <li>If not found, look for message in child property.  This is determined by evaluating
-     * the message key as an OGNL expression.  For example, if the key is
-     * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
-     * object.  If so, repeat the entire process fromthe beginning with the object's class as
-     * aClass and "address.state" as the message key.</li>
-     * <li>If not found, look for the message in aClass' package hierarchy.</li>
-     * <li>If still not found, look for the message in the default resource bundles.</li>
-     * <li>Return defaultMessage</li>
-     * </ol>
-     *
-     * <p>
-     * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
-     * message for that specific key cannot be found, the general form will also be looked up
-     * (i.e. user.phone[*]).
-     * </p>
-     *
-     * <p>
-     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
-     * will be treated as an OGNL expression and evaluated as such.
-     * </p>
-     *
-     * <p>
-     * If a message is <b>not</b> found a WARN log will be logged.
-     * </p>
-     *
-     * @param aClass         the class whose name to use as the start point for the search
-     * @param aTextName      the key to find the text message for
-     * @param locale         the locale the message should be for
-     * @param defaultMessage the message to be returned if no text message can be found in any
-     *                       resource bundle
-     * @param args           arguments
-     * @param valueStack     the value stack to use to evaluate expressions instead of the
-     *                       one in the ActionContext ThreadLocal
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     */
-    @Override
-    public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args,
-                           ValueStack valueStack) {
-        String indexedTextName = null;
-        if (aTextName == null) {
-        	LOG.warn("Trying to find text with null key!");
-            aTextName = "";
-        }
-        // calculate indexedTextName (collection[*]) if applicable
-        if (aTextName.contains("[")) {
-            int i = -1;
-
-            indexedTextName = aTextName;
-
-            while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
-                int j = indexedTextName.indexOf("]", i);
-                String a = indexedTextName.substring(0, i);
-                String b = indexedTextName.substring(j);
-                indexedTextName = a + "[*" + b;
-            }
-        }
-
-        // search up class hierarchy
-        String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
-
-        if (msg != null) {
-            return msg;
-        }
-
-        if (ModelDriven.class.isAssignableFrom(aClass)) {
-            ActionContext context = ActionContext.getContext();
-            // search up model's class hierarchy
-            ActionInvocation actionInvocation = context.getActionInvocation();
-
-            // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null
-            if (actionInvocation != null) {
-                Object action = actionInvocation.getAction();
-                if (action instanceof ModelDriven) {
-                    Object model = ((ModelDriven) action).getModel();
-                    if (model != null) {
-                        msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack);
-                        if (msg != null) {
-                            return msg;
-                        }
-                    }
-                }
-            }
-        }
-
-        // nothing still? alright, search the package hierarchy now
-        for (Class clazz = aClass;
-             (clazz != null) && !clazz.equals(Object.class);
-             clazz = clazz.getSuperclass()) {
-
-            String basePackageName = clazz.getName();
-            while (basePackageName.lastIndexOf('.') != -1) {
-                basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.'));
-                String packageName = basePackageName + ".package";
-                msg = getMessage(packageName, locale, aTextName, valueStack, args);
-
-                if (msg != null) {
-                    return msg;
-                }
-
-                if (indexedTextName != null) {
-                    msg = getMessage(packageName, locale, indexedTextName, valueStack, args);
-
-                    if (msg != null) {
-                        return msg;
-                    }
-                }
-            }
-        }
-
-        // see if it's a child property
-        int idx = aTextName.indexOf(".");
-
-        if (idx != -1) {
-            String newKey = null;
-            String prop = null;
-
-            if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
-                idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
-
-                if (idx != -1) {
-                    prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
-                    newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1);
-                }
-            } else {
-                prop = aTextName.substring(0, idx);
-                newKey = aTextName.substring(idx + 1);
-            }
-
-            if (prop != null) {
-                Object obj = valueStack.findValue(prop);
-                try {
-                    Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot());
-                    if (actionObj != null) {
-                        PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop);
-
-                        if (propertyDescriptor != null) {
-                            Class clazz = propertyDescriptor.getPropertyType();
-
-                            if (clazz != null) {
-                                if (obj != null) {
-                                    valueStack.push(obj);
-                                }
-                                msg = findText(clazz, newKey, locale, null, args);
-                                if (obj != null) {
-                                    valueStack.pop();
-                                }
-                                if (msg != null) {
-                                    return msg;
-                                }
-                            }
-                        }
-                    }
-                } catch (Exception e) {
-                    LOG.debug("unable to find property {}", prop, e);
-                }
-            }
-        }
-
-        // get default
-        GetDefaultMessageReturnArg result;
-        if (indexedTextName == null) {
-            result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
-        } else {
-            result = getDefaultMessage(aTextName, locale, valueStack, args, null);
-            if (result != null && result.message != null) {
-                return result.message;
-            }
-            result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
-        }
-
-        // could we find the text, if not log a warn
-        if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
-            String warn = "Unable to find text for key '" + aTextName + "' ";
-            if (indexedTextName != null) {
-                warn += " or indexed key '" + indexedTextName + "' ";
-            }
-            warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
-            LOG.debug(warn);
-        }
-
-        return result != null ? result.message : null;
-    }
-
-    /**
-     * Determines if we found the text in the bundles.
-     *
-     * @param result the result so far
-     * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
-     */
-    private boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
-        if (result == null || result.message == null) {
-            return true;
-        }
-
-        // did we find it in the bundle, then no problem?
-        if (result.foundInBundle) {
-            return false;
-        }
-
-        // not found in bundle
-        return true;
-    }
-
-    /**
-     * <p>
-     * Finds a localized text message for the given key, aTextName, in the specified resource bundle
-     * with aTextName as the default message.
-     * </p>
-     *
-     * <p>
-     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
-     * will be treated as an OGNL expression and evaluated as such.
-     * </p>
-     *
-     * @param bundle a resource bundle name
-     * @param aTextName  text name
-     * @param locale the locale
-     *
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
-     */
-    @Override
-    public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
-        return findText(bundle, aTextName, locale, aTextName, new Object[0]);
-    }
-
-    /**
-     * <p>
-     * Finds a localized text message for the given key, aTextName, in the specified resource
-     * bundle.
-     * </p>
-     *
-     * <p>
-     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
-     * will be treated as an OGNL expression and evaluated as such.
-     * </p>
-     *
-     * <p>
-     * If a message is <b>not</b> found a WARN log will be logged.
-     * </p>
-     *
-     * @param bundle         the bundle
-     * @param aTextName      the key
-     * @param locale         the locale
-     * @param defaultMessage the default message to use if no message was found in the bundle
-     * @param args           arguments for the message formatter.
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     */
-    @Override
-    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
-        ValueStack valueStack = ActionContext.getContext().getValueStack();
-        return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
-    }
-
-    /**
-     * <p>
-     * Finds a localized text message for the given key, aTextName, in the specified resource
-     * bundle.
-     * </p>
-     *
-     * <p>
-     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
-     * will be treated as an OGNL expression and evaluated as such.
-     * </p>
-     *
-     * <p>
-     * If a message is <b>not</b> found a WARN log will be logged.
-     * </p>
-     *
-     * @param bundle         the bundle
-     * @param aTextName      the key
-     * @param locale         the locale
-     * @param defaultMessage the default message to use if no message was found in the bundle
-     * @param args           arguments for the message formatter.
-     * @param valueStack     the OGNL value stack.
-     * @return the localized text, or null if none can be found and no defaultMessage is provided
-     */
-    @Override
-    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
-                           ValueStack valueStack) {
-        try {
-            reloadBundles(valueStack.getContext());
-
-            String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
-            MessageFormat mf = buildMessageFormat(message, locale);
-
-            return formatWithNullDetection(mf, args);
-        } catch (MissingResourceException ex) {
-            if (devMode) {
-                LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
-            } else {
-                LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
-            }
-        }
-
-        GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
-        if (unableToFindTextForKey(result)) {
-            LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
-        }
-        return result != null ? result.message : null;
-    }
-
-    /**
-     * @return the default message.
-     */
-    private GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
-                                                                String defaultMessage) {
-        GetDefaultMessageReturnArg result = null;
-        boolean found = true;
-
-        if (key != null) {
-            String message = findDefaultText(key, locale);
-
-            if (message == null) {
-                message = defaultMessage;
-                found = false; // not found in bundles
-            }
-
-            // defaultMessage may be null
-            if (message != null) {
-                MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
-
-                String msg = formatWithNullDetection(mf, args);
-                result = new GetDefaultMessageReturnArg(msg, found);
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * @return the message from the named resource bundle.
-     */
-    private String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
-        ResourceBundle bundle = findResourceBundle(bundleName, locale);
-        if (bundle == null) {
-            return null;
-        }
-        if (valueStack != null) 
-            reloadBundles(valueStack.getContext());
-        try {
-        	String message = bundle.getString(key);
-        	if (valueStack != null) 
-        		message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
-            MessageFormat mf = buildMessageFormat(message, locale);
-            return formatWithNullDetection(mf, args);
-        } catch (MissingResourceException e) {
-            if (devMode) {
-                LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
-            } else {
-                LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
-            }
-            return null;
-        }
-    }
-
-    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.
-     *
-     * @return the message
-     */
-    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>();
-        } else if (checked.contains(clazz.getName())) {
-            return null;
-        }
-
-        // look in properties of this class
-        String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
-
-        if (msg != null) {
-            return msg;
-        }
-
-        if (indexedKey != null) {
-            msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
-
-            if (msg != null) {
-                return msg;
-            }
-        }
-
-        // look in properties of implemented interfaces
-        Class[] interfaces = clazz.getInterfaces();
-
-        for (Class anInterface : interfaces) {
-            msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
-
-            if (msg != null) {
-                return msg;
-            }
-
-            if (indexedKey != null) {
-                msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
-
-                if (msg != null) {
-                    return msg;
-                }
-            }
-        }
-
-        // traverse up hierarchy
-        if (clazz.isInterface()) {
-            interfaces = clazz.getInterfaces();
-
-            for (Class anInterface : interfaces) {
-                msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
-
-                if (msg != null) {
-                    return msg;
-                }
-            }
-        } else {
-            if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
-                return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
-            }
-        }
-
-        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.
-     *
-     * @deprecated used only in tests
-     */
-    @Deprecated
-    public void reset() {
-        // 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
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/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
new file mode 100644
index 0000000..78bf08d
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -0,0 +1,1000 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+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;
+
+/**
+ * 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 {
+
+    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.
+     *
+     * @deprecated used only in tests
+     */
+    @Deprecated
+    public static void clearDefaultResourceBundles() {
+        // no-op
+    }
+
+    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) {
+            StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
+
+            while (customBundles.hasMoreTokens()) {
+                String name = customBundles.nextToken();
+                try {
+                    LOG.trace("Loading global messages from [{}]", name);
+                    addDefaultResourceBundle(name);
+                } catch (Exception e) {
+                    LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 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()}.
+     *
+     * @param localeStr     The locale String to parse.
+     * @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
+     * @return requested Locale
+     *
+     * @deprecated please use {@link org.apache.commons.lang3.LocaleUtils#toLocale(String)}
+     */
+    @Deprecated
+    public static Locale localeFromString(String localeStr, Locale defaultLocale) {
+        if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) {
+            if (defaultLocale != null) {
+                return defaultLocale;
+            }
+            return Locale.getDefault();
+        }
+
+        int index = localeStr.indexOf('_');
+        if (index < 0) {
+            return new Locale(localeStr);
+        }
+
+        String language = localeStr.substring(0, index);
+        if (index == localeStr.length()) {
+            return new Locale(language);
+        }
+
+        localeStr = localeStr.substring(index + 1);
+        index = localeStr.indexOf('_');
+        if (index < 0) {
+            return new Locale(language, localeStr);
+        }
+
+        String country = localeStr.substring(0, index);
+        if (index == localeStr.length()) {
+            return new Locale(language, country);
+        }
+
+        localeStr = localeStr.substring(index + 1);
+        return new Locale(language, country, localeStr);
+    }
+
+    /**
+     * 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.
+     *
+     * @param aClass class name
+     * @param aTextName  text name
+     * @param locale the locale
+     *
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
+     */
+    @Override
+    public String findText(Class aClass, String aTextName, Locale locale) {
+        return findText(aClass, aTextName, locale, aTextName, new Object[0]);
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName. Both the key and the message
+     * itself is evaluated as required.  The following algorithm is used to find the requested
+     * message:
+     * </p>
+     *
+     * <ol>
+     * <li>Look for message in aClass' class hierarchy.
+     * <ol>
+     * <li>Look for the message in a resource bundle for aClass</li>
+     * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+     * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+     * </ol></li>
+     * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+     * the model's class hierarchy (repeat sub-steps listed above).</li>
+     * <li>If not found, look for message in child property.  This is determined by evaluating
+     * the message key as an OGNL expression.  For example, if the key is
+     * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+     * object.  If so, repeat the entire process fromthe beginning with the object's class as
+     * aClass and "address.state" as the message key.</li>
+     * <li>If not found, look for the message in aClass' package hierarchy.</li>
+     * <li>If still not found, look for the message in the default resource bundles.</li>
+     * <li>Return defaultMessage</li>
+     * </ol>
+     *
+     * <p>
+     * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+     * message for that specific key cannot be found, the general form will also be looked up
+     * (i.e. user.phone[*]).
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * @param aClass         the class whose name to use as the start point for the search
+     * @param aTextName      the key to find the text message for
+     * @param locale         the locale the message should be for
+     * @param defaultMessage the message to be returned if no text message can be found in any
+     *                       resource bundle
+     * @param args           arguments
+     *                       resource bundle
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+        ValueStack valueStack = ActionContext.getContext().getValueStack();
+        return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
+
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName. Both the key and the message
+     * itself is evaluated as required.  The following algorithm is used to find the requested
+     * message:
+     * </p>
+     *
+     * <ol>
+     * <li>Look for message in aClass' class hierarchy.
+     * <ol>
+     * <li>Look for the message in a resource bundle for aClass</li>
+     * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+     * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+     * </ol></li>
+     * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+     * the model's class hierarchy (repeat sub-steps listed above).</li>
+     * <li>If not found, look for message in child property.  This is determined by evaluating
+     * the message key as an OGNL expression.  For example, if the key is
+     * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+     * object.  If so, repeat the entire process fromthe beginning with the object's class as
+     * aClass and "address.state" as the message key.</li>
+     * <li>If not found, look for the message in aClass' package hierarchy.</li>
+     * <li>If still not found, look for the message in the default resource bundles.</li>
+     * <li>Return defaultMessage</li>
+     * </ol>
+     *
+     * <p>
+     * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+     * message for that specific key cannot be found, the general form will also be looked up
+     * (i.e. user.phone[*]).
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * <p>
+     * If a message is <b>not</b> found a WARN log will be logged.
+     * </p>
+     *
+     * @param aClass         the class whose name to use as the start point for the search
+     * @param aTextName      the key to find the text message for
+     * @param locale         the locale the message should be for
+     * @param defaultMessage the message to be returned if no text message can be found in any
+     *                       resource bundle
+     * @param args           arguments
+     * @param valueStack     the value stack to use to evaluate expressions instead of the
+     *                       one in the ActionContext ThreadLocal
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args,
+                           ValueStack valueStack) {
+        String indexedTextName = null;
+        if (aTextName == null) {
+        	LOG.warn("Trying to find text with null key!");
+            aTextName = "";
+        }
+        // calculate indexedTextName (collection[*]) if applicable
+        if (aTextName.contains("[")) {
+            int i = -1;
+
+            indexedTextName = aTextName;
+
+            while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
+                int j = indexedTextName.indexOf("]", i);
+                String a = indexedTextName.substring(0, i);
+                String b = indexedTextName.substring(j);
+                indexedTextName = a + "[*" + b;
+            }
+        }
+
+        // search up class hierarchy
+        String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
+
+        if (msg != null) {
+            return msg;
+        }
+
+        if (ModelDriven.class.isAssignableFrom(aClass)) {
+            ActionContext context = ActionContext.getContext();
+            // search up model's class hierarchy
+            ActionInvocation actionInvocation = context.getActionInvocation();
+
+            // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null
+            if (actionInvocation != null) {
+                Object action = actionInvocation.getAction();
+                if (action instanceof ModelDriven) {
+                    Object model = ((ModelDriven) action).getModel();
+                    if (model != null) {
+                        msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack);
+                        if (msg != null) {
+                            return msg;
+                        }
+                    }
+                }
+            }
+        }
+
+        // nothing still? alright, search the package hierarchy now
+        for (Class clazz = aClass;
+             (clazz != null) && !clazz.equals(Object.class);
+             clazz = clazz.getSuperclass()) {
+
+            String basePackageName = clazz.getName();
+            while (basePackageName.lastIndexOf('.') != -1) {
+                basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.'));
+                String packageName = basePackageName + ".package";
+                msg = getMessage(packageName, locale, aTextName, valueStack, args);
+
+                if (msg != null) {
+                    return msg;
+                }
+
+                if (indexedTextName != null) {
+                    msg = getMessage(packageName, locale, indexedTextName, valueStack, args);
+
+                    if (msg != null) {
+                        return msg;
+                    }
+                }
+            }
+        }
+
+        // see if it's a child property
+        int idx = aTextName.indexOf(".");
+
+        if (idx != -1) {
+            String newKey = null;
+            String prop = null;
+
+            if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
+                idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
+
+                if (idx != -1) {
+                    prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
+                    newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1);
+                }
+            } else {
+                prop = aTextName.substring(0, idx);
+                newKey = aTextName.substring(idx + 1);
+            }
+
+            if (prop != null) {
+                Object obj = valueStack.findValue(prop);
+                try {
+                    Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot());
+                    if (actionObj != null) {
+                        PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop);
+
+                        if (propertyDescriptor != null) {
+                            Class clazz = propertyDescriptor.getPropertyType();
+
+                            if (clazz != null) {
+                                if (obj != null) {
+                                    valueStack.push(obj);
+                                }
+                                msg = findText(clazz, newKey, locale, null, args);
+                                if (obj != null) {
+                                    valueStack.pop();
+                                }
+                                if (msg != null) {
+                                    return msg;
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    LOG.debug("unable to find property {}", prop, e);
+                }
+            }
+        }
+
+        // get default
+        GetDefaultMessageReturnArg result;
+        if (indexedTextName == null) {
+            result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+        } else {
+            result = getDefaultMessage(aTextName, locale, valueStack, args, null);
+            if (result != null && result.message != null) {
+                return result.message;
+            }
+            result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
+        }
+
+        // could we find the text, if not log a warn
+        if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
+            String warn = "Unable to find text for key '" + aTextName + "' ";
+            if (indexedTextName != null) {
+                warn += " or indexed key '" + indexedTextName + "' ";
+            }
+            warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
+            LOG.debug(warn);
+        }
+
+        return result != null ? result.message : null;
+    }
+
+    /**
+     * Determines if we found the text in the bundles.
+     *
+     * @param result the result so far
+     * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
+     */
+    private boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
+        if (result == null || result.message == null) {
+            return true;
+        }
+
+        // did we find it in the bundle, then no problem?
+        if (result.foundInBundle) {
+            return false;
+        }
+
+        // not found in bundle
+        return true;
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName, in the specified resource bundle
+     * with aTextName as the default message.
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * @param bundle a resource bundle name
+     * @param aTextName  text name
+     * @param locale the locale
+     *
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
+     */
+    @Override
+    public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
+        return findText(bundle, aTextName, locale, aTextName, new Object[0]);
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName, in the specified resource
+     * bundle.
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * <p>
+     * If a message is <b>not</b> found a WARN log will be logged.
+     * </p>
+     *
+     * @param bundle         the bundle
+     * @param aTextName      the key
+     * @param locale         the locale
+     * @param defaultMessage the default message to use if no message was found in the bundle
+     * @param args           arguments for the message formatter.
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+        ValueStack valueStack = ActionContext.getContext().getValueStack();
+        return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
+    }
+
+    /**
+     * <p>
+     * Finds a localized text message for the given key, aTextName, in the specified resource
+     * bundle.
+     * </p>
+     *
+     * <p>
+     * If a message is found, it will also be interpolated.  Anything within <code>${...}</code>
+     * will be treated as an OGNL expression and evaluated as such.
+     * </p>
+     *
+     * <p>
+     * If a message is <b>not</b> found a WARN log will be logged.
+     * </p>
+     *
+     * @param bundle         the bundle
+     * @param aTextName      the key
+     * @param locale         the locale
+     * @param defaultMessage the default message to use if no message was found in the bundle
+     * @param args           arguments for the message formatter.
+     * @param valueStack     the OGNL value stack.
+     * @return the localized text, or null if none can be found and no defaultMessage is provided
+     */
+    @Override
+    public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
+                           ValueStack valueStack) {
+        try {
+            reloadBundles(valueStack.getContext());
+
+            String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
+            MessageFormat mf = buildMessageFormat(message, locale);
+
+            return formatWithNullDetection(mf, args);
+        } catch (MissingResourceException ex) {
+            if (devMode) {
+                LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+            } else {
+                LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+            }
+        }
+
+        GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+        if (unableToFindTextForKey(result)) {
+            LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
+        }
+        return result != null ? result.message : null;
+    }
+
+    /**
+     * @return the default message.
+     */
+    private GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
+                                                                String defaultMessage) {
+        GetDefaultMessageReturnArg result = null;
+        boolean found = true;
+
+        if (key != null) {
+            String message = findDefaultText(key, locale);
+
+            if (message == null) {
+                message = defaultMessage;
+                found = false; // not found in bundles
+            }
+
+            // defaultMessage may be null
+            if (message != null) {
+                MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
+
+                String msg = formatWithNullDetection(mf, args);
+                result = new GetDefaultMessageReturnArg(msg, found);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * @return the message from the named resource bundle.
+     */
+    private String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
+        ResourceBundle bundle = findResourceBundle(bundleName, locale);
+        if (bundle == null) {
+            return null;
+        }
+        if (valueStack != null) 
+            reloadBundles(valueStack.getContext());
+        try {
+        	String message = bundle.getString(key);
+        	if (valueStack != null) 
+        		message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
+            MessageFormat mf = buildMessageFormat(message, locale);
+            return formatWithNullDetection(mf, args);
+        } catch (MissingResourceException e) {
+            if (devMode) {
+                LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
+            } else {
+                LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
+            }
+            return null;
+        }
+    }
+
+    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.
+     *
+     * @return the message
+     */
+    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>();
+        } else if (checked.contains(clazz.getName())) {
+            return null;
+        }
+
+        // look in properties of this class
+        String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
+
+        if (msg != null) {
+            return msg;
+        }
+
+        if (indexedKey != null) {
+            msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
+
+            if (msg != null) {
+                return msg;
+            }
+        }
+
+        // look in properties of implemented interfaces
+        Class[] interfaces = clazz.getInterfaces();
+
+        for (Class anInterface : interfaces) {
+            msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
+
+            if (msg != null) {
+                return msg;
+            }
+
+            if (indexedKey != null) {
+                msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
+
+                if (msg != null) {
+                    return msg;
+                }
+            }
+        }
+
+        // traverse up hierarchy
+        if (clazz.isInterface()) {
+            interfaces = clazz.getInterfaces();
+
+            for (Class anInterface : interfaces) {
+                msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
+
+                if (msg != null) {
+                    return msg;
+                }
+            }
+        } else {
+            if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
+                return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
+            }
+        }
+
+        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.
+     *
+     * @deprecated used only in tests
+     */
+    @Deprecated
+    public void reset() {
+        // 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
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/resources/struts-default.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index 362e317..ead2954 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -131,7 +131,7 @@
     <bean type="com.opensymphony.xwork2.conversion.impl.StringConverter" name="struts" class="com.opensymphony.xwork2.conversion.impl.StringConverter" scope="singleton"/>
 
     <bean type="com.opensymphony.xwork2.TextProvider" name="system" class="com.opensymphony.xwork2.DefaultTextProvider" scope="singleton" />
-    <bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="struts" class="com.opensymphony.xwork2.util.DefaultLocalizedTextProvider" scope="singleton" />
+    <bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="struts" class="com.opensymphony.xwork2.util.StrutsLocalizedTextProvider" scope="singleton" />
     <bean type="com.opensymphony.xwork2.TextProviderFactory" name="struts" class="com.opensymphony.xwork2.StrutsTextProviderFactory" scope="singleton" />
     <bean type="com.opensymphony.xwork2.LocaleProviderFactory" name="struts" class="com.opensymphony.xwork2.DefaultLocaleProviderFactory" scope="singleton" />
 


[05/10] struts git commit: WW-4762 Extracts base abstract class

Posted by lu...@apache.org.
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/master
Commit: 2e23d7a07ea4313136dc38b8de2451b04c882064
Parents: 8bf77a1
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 12:24:57 2017 +0200
Committer: Lukasz Lenart <lu...@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
-        }
-    }
-
 }


[02/10] struts git commit: WW-4762 Drops unused imports

Posted by lu...@apache.org.
WW-4762 Drops unused imports


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/3ffc9b84
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/3ffc9b84
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/3ffc9b84

Branch: refs/heads/master
Commit: 3ffc9b8450845d7a90a623607e3b4bb05888e2f9
Parents: 45d1a49
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 11:56:09 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 11:56:09 2017 +0200

----------------------------------------------------------------------
 .../java/com/opensymphony/xwork2/DefaultTextProvider.java    | 8 +-------
 .../main/java/org/apache/struts2/dispatcher/Dispatcher.java  | 1 -
 .../struts2/config/PropertiesConfigurationProviderTest.java  | 1 -
 3 files changed, 1 insertion(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/3ffc9b84/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
index 51ed449..116a0a1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
@@ -16,7 +16,6 @@
 package com.opensymphony.xwork2;
 
 import com.opensymphony.xwork2.inject.Inject;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
 import com.opensymphony.xwork2.util.ValueStack;
 
 import java.io.Serializable;
@@ -27,12 +26,7 @@ import java.util.List;
 import java.util.ResourceBundle;
 
 /**
- * DefaultTextProvider gets texts from only the default resource bundles associated with the
- * LocalizedTextUtil.
- *
- * @author Jason Carreira jcarreira@gmail.com
- * @author Rainer Hermanns
- * @see DefaultLocalizedTextProvider#addDefaultResourceBundle(String)
+ * DefaultTextProvider gets texts from only the default resource bundles associated with the default bundles.
  */
 public class DefaultTextProvider implements TextProvider, Serializable, Unchainable {
 

http://git-wip-us.apache.org/repos/asf/struts/blob/3ffc9b84/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
index 667f37d..55707a4 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
@@ -32,7 +32,6 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
 import com.opensymphony.xwork2.inject.Inject;
 import com.opensymphony.xwork2.interceptor.Interceptor;
 import com.opensymphony.xwork2.util.ClassLoaderUtil;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
 import com.opensymphony.xwork2.util.ValueStack;
 import com.opensymphony.xwork2.util.ValueStackFactory;
 import com.opensymphony.xwork2.util.location.LocatableProperties;

http://git-wip-us.apache.org/repos/asf/struts/blob/3ffc9b84/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java b/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
index b4fd408..a82a8c6 100644
--- a/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
+++ b/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
@@ -23,7 +23,6 @@ package org.apache.struts2.config;
 
 import com.opensymphony.xwork2.inject.Container;
 import com.opensymphony.xwork2.inject.ContainerBuilder;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
 import com.opensymphony.xwork2.util.location.LocatableProperties;
 import junit.framework.TestCase;
 import org.apache.commons.lang3.LocaleUtils;