Merge pull request #466 from apache/WW-5056-allows-dash

[WW-5056] Accepts dashes in param names
diff --git a/core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java b/core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java
index 0d9f6c7..3a5c8f2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ObjectFactory.java
@@ -31,6 +31,7 @@
 import com.opensymphony.xwork2.validator.Validator;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.apache.struts2.StrutsConstants;
 
 import java.io.Serializable;
 import java.util.Map;
@@ -68,7 +69,7 @@
         this.container = container;
     }
 
-    @Inject(value="objectFactory.classloader", required=false)
+    @Inject(value=StrutsConstants.STRUTS_OBJECT_FACTORY_CLASSLOADER, required=false)
     public void setClassLoader(ClassLoader cl) {
         this.ccl = cl;
     }
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 38d250a..bfbec00 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
@@ -284,7 +284,7 @@
         builder.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON);
 
         builder.constant(StrutsConstants.STRUTS_DEVMODE, "false");
-        builder.constant(StrutsConstants.STRUTS_LOG_MISSING_PROPERTIES, "false");
+        builder.constant(StrutsConstants.STRUTS_OGNL_LOG_MISSING_PROPERTIES, "false");
         builder.constant(StrutsConstants.STRUTS_ENABLE_OGNL_EVAL_EXPRESSION, "false");
         builder.constant(StrutsConstants.STRUTS_ENABLE_OGNL_EXPRESSION_CACHE, "true");
         builder.constant(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, "false");
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
index ccdb782..f263df3 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
@@ -219,7 +219,7 @@
         props.setProperty(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, Boolean.FALSE.toString());
         props.setProperty(StrutsConstants.STRUTS_I18N_RELOAD, Boolean.FALSE.toString());
         props.setProperty(StrutsConstants.STRUTS_DEVMODE, Boolean.FALSE.toString());
-        props.setProperty(StrutsConstants.STRUTS_LOG_MISSING_PROPERTIES, Boolean.FALSE.toString());
+        props.setProperty(StrutsConstants.STRUTS_OGNL_LOG_MISSING_PROPERTIES, Boolean.FALSE.toString());
         props.setProperty(StrutsConstants.STRUTS_ENABLE_OGNL_EXPRESSION_CACHE, Boolean.TRUE.toString());
         props.setProperty(StrutsConstants.STRUTS_ENABLE_OGNL_EVAL_EXPRESSION, Boolean.FALSE.toString());
         props.setProperty(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, Boolean.FALSE.toString());
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java
index ad99281..31b0075 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java
@@ -30,6 +30,7 @@
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
 import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import org.apache.struts2.StrutsConstants;
 
 import java.util.*;
 
@@ -59,9 +60,9 @@
  * </p>
  *
  * <ul>
- * <li>struts.xwork.chaining.copyErrors - set to true to copy Action Errors</li>
- * <li>struts.xwork.chaining.copyFieldErrors - set to true to copy Field Errors</li>
- * <li>struts.xwork.chaining.copyMessages - set to true to copy Action Messages</li>
+ * <li>struts.chaining.copyErrors - set to true to copy Action Errors</li>
+ * <li>struts.chaining.copyFieldErrors - set to true to copy Field Errors</li>
+ * <li>struts.chaining.copyMessages - set to true to copy Action Messages</li>
  * </ul>
  *
  * <p>
@@ -135,17 +136,17 @@
         this.reflectionProvider = prov;
     }
 
-    @Inject(value = "struts.xwork.chaining.copyErrors", required = false)
+    @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_ERRORS, required = false)
     public void setCopyErrors(String copyErrors) {
         this.copyErrors = "true".equalsIgnoreCase(copyErrors);
     }
 
-    @Inject(value = "struts.xwork.chaining.copyFieldErrors", required = false)
+    @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_FIELD_ERRORS, required = false)
     public void setCopyFieldErrors(String copyFieldErrors) {
         this.copyFieldErrors = "true".equalsIgnoreCase(copyFieldErrors);
     }
 
-    @Inject(value = "struts.xwork.chaining.copyMessages", required = false)
+    @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_MESSAGES, required = false)
     public void setCopyMessages(String copyMessages) {
         this.copyMessages = "true".equalsIgnoreCase(copyMessages);
     }
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java
index 036407b..c08da93 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java
@@ -105,7 +105,7 @@
         this.devMode = BooleanUtils.toBoolean(mode);
     }
 
-    @Inject(value = "logMissingProperties", required = false)
+    @Inject(value = StrutsConstants.STRUTS_OGNL_LOG_MISSING_PROPERTIES, required = false)
     protected void setLogMissingProperties(String logMissingProperties) {
         this.logMissingProperties = BooleanUtils.toBoolean(logMissingProperties);
     }
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java
index 6f3a682..ece1a92 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java
@@ -51,12 +51,6 @@
     private OgnlUtil ognlUtil;
     private int autoGrowCollectionLimit = 255;
 
-    @Deprecated()
-    @Inject(value = "xwork.autoGrowCollectionLimit", required = false)
-    public void setDeprecatedAutoGrowCollectionLimit(String value) {
-        this.autoGrowCollectionLimit = Integer.valueOf(value);
-    }
-    
     @Inject(value = StrutsConstants.STRUTS_OGNL_AUTO_GROWTH_COLLECTION_LIMIT, required = false)
     public void setAutoGrowCollectionLimit(String value) {
         this.autoGrowCollectionLimit = Integer.parseInt(value);
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 6ed6202..d197d05 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -61,6 +61,7 @@
     protected final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<>();
     protected boolean devMode = false;
     protected boolean reloadBundles = false;
+    protected boolean searchDefaultBundlesFirst = false;  // Search default resource bundles first.  Note: This flag may not be meaningful to all implementations.
 
     private final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<>();
     private final ConcurrentMap<Integer, List<String>> classLoaderMap = new ConcurrentHashMap<>();
@@ -68,7 +69,7 @@
     private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>();
 
     /**
-     * Add's the bundle to the internal list of default bundles.
+     * Adds 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.
@@ -431,6 +432,20 @@
     }
 
     /**
+     * Set the {@link #searchDefaultBundlesFirst} flag state.  This flag may be used by descendant TextProvider
+     * implementations to determine if default bundles should be searched for messages first (before the standard
+     * flow of the {@link LocalizedTextProvider} implementation the descendant provides).
+     * 
+     * @param searchDefaultBundlesFirst provide {@link String} "true" or "false" to set the flag state accordingly.
+     * 
+     * @since 2.6
+     */
+    @Inject(value = StrutsConstants.STRUTS_I18N_SEARCH_DEFAULTBUNDLES_FIRST, required = false)
+    public void setSearchDefaultBundlesFirst(String searchDefaultBundlesFirst) {
+        this.searchDefaultBundlesFirst = Boolean.parseBoolean(searchDefaultBundlesFirst);
+    }
+
+    /**
      * Finds the given resource bundle by it's name.
      * <p>
      * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
@@ -549,6 +564,42 @@
     }
 
     /**
+     * A helper method that can be used by descendant classes to perform some common two-stage message lookup operations
+     * against the default resource bundles.  The default resource bundles are searched for a value using key first, then
+     * alternateKey when the first search fails, then utilizing defaultMessage (which may be <code>null</code>) if <em>both</em>
+     * key lookup operations fail.
+     * 
+     * <p>
+     * A known use case is when a key indexes a collection (e.g. user.phone[0]) for which some specific keys may exist, but not all,
+     * along with a general key (e.g. user.phone[*]).  In such cases the specific key would be passed in the key parameter and the
+     * general key would be passed in the alternateKey parameter.
+     * </p>
+     * 
+     * @param key             the initial key to search for a value within the default resource bundles.
+     * @param alternateKey    the alternate (fall-back) key to search for a value within the default resource bundles, if the initial key lookup fails.
+     * @param locale          the {@link Locale} to be used for the default resource bundle lookup.
+     * @param valueStack      the {@link ValueStack} associated with the operation. 
+     * @param args            the argument array for parameterized messages (may be <code>null</code>).
+     * @param defaultMessage  the default message {@link String} to use if both key lookup operations fail.
+     * @return the {@link GetDefaultMessageReturnArg} result containing the processed message lookup (by key first, then alternateKey if key's lookup fails).
+     *         If both key lookup operations fail, defaultMessage is used for processing.
+     *         If defaultMessage is <code>null</code> then the return result may be <code>null</code>.
+     */
+    protected GetDefaultMessageReturnArg getDefaultMessageWithAlternateKey(String key, String alternateKey, Locale locale, ValueStack valueStack,
+            Object[] args, String defaultMessage) {
+        GetDefaultMessageReturnArg result;
+        if (alternateKey == null || alternateKey.isEmpty()) {
+            result = getDefaultMessage(key, locale, valueStack, args, defaultMessage);
+        } else {
+            result = getDefaultMessage(key, locale, valueStack, args, null);
+            if (result == null || result.message == null) {
+                result = getDefaultMessage(alternateKey, locale, valueStack, args, defaultMessage);
+            }
+        }
+        return result;
+    }
+
+    /**
      * @return the message from the named resource bundle.
      */
     protected String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
@@ -556,12 +607,14 @@
         if (bundle == null) {
             return null;
         }
-        if (valueStack != null)
+        if (valueStack != null) {
             reloadBundles(valueStack.getContext());
+        }
         try {
-        	String message = bundle.getString(key);
-        	if (valueStack != null)
-        		message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
+            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) {
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/GlobalLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/GlobalLocalizedTextProvider.java
index fce1b99..d86682f 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/GlobalLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/GlobalLocalizedTextProvider.java
@@ -19,7 +19,6 @@
 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;
 
@@ -27,8 +26,10 @@
 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.
+ * Provides support for localization in the framework, it can be used to read only default bundles.
+ * 
+ * Note that unlike {@link StrutsLocalizedTextProvider}, this class {@link GlobalLocalizedTextProvider} will
+ * <em>only</em> search the default bundles for localized text.
  */
 public class GlobalLocalizedTextProvider extends AbstractLocalizedTextProvider {
 
@@ -62,22 +63,8 @@
      * </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>
+     * <li>Look for the message in the default resource bundles.</li>
+     * <li>If not found, return defaultMessage</li>
      * </ol>
      *
      * <p>
@@ -115,22 +102,8 @@
      * </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>
+     * <li>Look for the message in the default resource bundles.</li>
+     * <li>If not found, return defaultMessage</li>
      * </ol>
      *
      * <p>
@@ -145,7 +118,7 @@
      * </p>
      *
      * <p>
-     * If a message is <b>not</b> found a WARN log will be logged.
+     * If a message is <b>not</b> found a DEBUG level log warning will be logged.
      * </p>
      *
      * @param aClass         the class whose name to use as the start point for the search
@@ -180,16 +153,7 @@
         }
 
         // 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);
-        }
+        GetDefaultMessageReturnArg result = getDefaultMessageWithAlternateKey(aTextName, indexedTextName, locale, valueStack, args, defaultMessage);
 
         // could we find the text, if not log a warn
         if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
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 1dc74b7..48acc42 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -27,9 +27,7 @@
 import org.apache.logging.log4j.Logger;
 
 import java.beans.PropertyDescriptor;
-import java.text.MessageFormat;
 import java.util.Locale;
-import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 
 /**
@@ -122,6 +120,7 @@
      * </p>
      *
      * <ol>
+     * <li>If {@link #searchDefaultBundlesFirst} is <code>true</code>, look for the message in the default resource bundles first.</li>
      * <li>Look for message in aClass' class hierarchy.
      * <ol>
      * <li>Look for the message in a resource bundle for aClass</li>
@@ -133,10 +132,11 @@
      * <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
+     * object.  If so, repeat the entire process from the 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>If still not found, look for the message in the default resource bundles 
+     * (Note: the lookup is not repeated again if {@link #searchDefaultBundlesFirst} was <code>true</code>).</li>
      * <li>Return defaultMessage</li>
      * </ol>
      *
@@ -175,6 +175,7 @@
      * </p>
      *
      * <ol>
+     * <li>If {@link #searchDefaultBundlesFirst} is <code>true</code>, look for the message in the default resource bundles first.</li>
      * <li>Look for message in aClass' class hierarchy.
      * <ol>
      * <li>Look for the message in a resource bundle for aClass</li>
@@ -186,10 +187,11 @@
      * <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
+     * object.  If so, repeat the entire process from the 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>If still not found, look for the message in the default resource bundles 
+     * (Note: the lookup is not repeated again if {@link #searchDefaultBundlesFirst} was <code>true</code>).</li>
      * <li>Return defaultMessage</li>
      * </ol>
      *
@@ -205,7 +207,7 @@
      * </p>
      *
      * <p>
-     * If a message is <b>not</b> found a WARN log will be logged.
+     * If a message is <b>not</b> found a DEBUG level log warning will be logged.
      * </p>
      *
      * @param aClass         the class whose name to use as the start point for the search
@@ -240,6 +242,20 @@
             }
         }
 
+        // Allow for and track an early lookup for the message in the default resource bundles first, before searching the class hierarchy.
+        // The early lookup is only performed when the text provider has been configured to do so, otherwise follow the standard processing order.
+        boolean performedInitialDefaultBundlesMessageLookup = false;
+        GetDefaultMessageReturnArg result = null;
+
+        // If search default bundles first is set true, call alternative logic first.
+        if (searchDefaultBundlesFirst) {
+            result = getDefaultMessageWithAlternateKey(aTextName, indexedTextName, locale, valueStack, args, defaultMessage);
+            performedInitialDefaultBundlesMessageLookup = true;
+            if (!unableToFindTextForKey(result)) {
+                return result.message;  // Found a message in the default resource bundles for aTextName or indexedTextName.
+            }
+        }
+
         // search up class hierarchy
         String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
 
@@ -342,15 +358,10 @@
         }
 
         // 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);
+        // Note: The default bundles lookup may already have been performed (via alternate early lookup),
+        //       so we check first to avoid repeating the same operation twice.
+        if (!performedInitialDefaultBundlesMessageLookup) {
+            result = getDefaultMessageWithAlternateKey(aTextName, indexedTextName, locale, valueStack, args, defaultMessage);
         }
 
         // could we find the text, if not log a warn
diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java
index 45b44a6..e5ed043 100644
--- a/core/src/main/java/org/apache/struts2/StrutsConstants.java
+++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java
@@ -35,6 +35,17 @@
     /** The encoding to use for localization messages */
     public static final String STRUTS_I18N_ENCODING = "struts.i18n.encoding";
 
+    /** 
+     * Whether the default bundles should be searched for messages first.  Can be used to modify the
+     * standard processing order for message lookup in TextProvider implementations.
+     * <p>
+     * Note: This control flag may not be meaningful to all provider implementations, and should be false by default.
+     * </p>
+     * 
+     * @since 2.6
+     */
+    public static final String STRUTS_I18N_SEARCH_DEFAULTBUNDLES_FIRST = "struts.i18n.search.defaultbundles.first";
+
     /** Whether to reload the XML configuration or not */
     public static final String STRUTS_CONFIGURATION_XML_RELOAD = "struts.configuration.xml.reload";
 
@@ -249,8 +260,18 @@
     /** Throw RuntimeException when a property is not found, or the evaluation of the expression fails */
     public static final String STRUTS_EL_THROW_EXCEPTION = "struts.el.throwExceptionOnFailure";
 
-    /** Logs properties that are not found (very verbose) */
-    public static final String STRUTS_LOG_MISSING_PROPERTIES = "struts.ognl.logMissingProperties";
+    /**
+     * Logs properties that are not found (very verbose)
+     * @since 2.6
+     */
+    public static final String STRUTS_OGNL_LOG_MISSING_PROPERTIES = "struts.ognl.logMissingProperties";
+
+    /**
+     * Logs properties that are not found (very verbose)
+     * @deprecated as of 2.6.  Use {@link #STRUTS_OGNL_LOG_MISSING_PROPERTIES} instead.
+     */
+    @Deprecated
+    public static final String STRUTS_LOG_MISSING_PROPERTIES = STRUTS_OGNL_LOG_MISSING_PROPERTIES;
 
     /** Enables caching of parsed OGNL expressions */
     public static final String STRUTS_ENABLE_OGNL_EXPRESSION_CACHE = "struts.ognl.enableExpressionCache";
@@ -346,4 +367,9 @@
 
     /** See {@link com.opensymphony.xwork2.config.impl.AbstractMatcher#appendNamedParameters */
     public static final String STRUTS_MATCHER_APPEND_NAMED_PARAMETERS = "struts.matcher.appendNamedParameters";
+
+    public static final String STRUTS_CHAINING_COPY_ERRORS = "struts.chaining.copyErrors";
+    public static final String STRUTS_CHAINING_COPY_FIELD_ERRORS = "struts.chaining.copyFieldErrors";
+    public static final String STRUTS_CHAINING_COPY_MESSAGES = "struts.chaining.copyMessages";
+    public static final String STRUTS_OBJECT_FACTORY_CLASSLOADER = "struts.objectFactory.classloader";
 }
diff --git a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
index 6d9be83..2842155 100644
--- a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
+++ b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
@@ -230,7 +230,7 @@
         map.put(StrutsConstants.STRUTS_STATIC_CONTENT_LOADER, beanConfToString(staticContentLoader));
         map.put(StrutsConstants.STRUTS_UNKNOWN_HANDLER_MANAGER, beanConfToString(unknownHandlerManager));
         map.put(StrutsConstants.STRUTS_EL_THROW_EXCEPTION, Objects.toString(elThrowExceptionOnFailure, null));
-        map.put(StrutsConstants.STRUTS_LOG_MISSING_PROPERTIES, Objects.toString(ognlLogMissingProperties, null));
+        map.put(StrutsConstants.STRUTS_OGNL_LOG_MISSING_PROPERTIES, Objects.toString(ognlLogMissingProperties, null));
         map.put(StrutsConstants.STRUTS_ENABLE_OGNL_EXPRESSION_CACHE, Objects.toString(ognlEnableExpressionCache, null));
         map.put(StrutsConstants.STRUTS_ENABLE_OGNL_EVAL_EXPRESSION, Objects.toString(ognlEnableOGNLEvalExpression, null));
         map.put(StrutsConstants.STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP, Objects.toString(disableRequestAttributeValueStackLookup, null));
diff --git a/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java b/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java
index 2d08d8f..1fa4110 100644
--- a/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java
+++ b/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java
@@ -46,27 +46,6 @@
      * @return the message if found, otherwise the defaultMessage
      */
     public static String getText(String key, String defaultMessage, List<Object> args, ValueStack stack) {
-        return getText(key, defaultMessage, args, stack, false);
-    }
-
-    /**
-     * <p>Get a message from the first TextProvider encountered in the stack.
-     * If the first TextProvider doesn't provide the message the default message is returned.</p>
-     * <p>The search for a TextProvider is iterative from the root of the stack.</p>
-     * <p>This method was refactored from  {@link org.apache.struts2.components.Text} to use a
-     * consistent implementation across UIBean components.</p>
-     * @param key             the message key in the resource bundle
-     * @param defaultMessage  the message to return if not found (evaluated for OGNL)
-     * @param args            an array args to be used in a {@link java.text.MessageFormat} message
-     * @param stack           the value stack to use for finding the text
-     * @param searchStack     search stack for the key
-     *
-     * @return the message if found, otherwise the defaultMessage
-     *
-     * @deprecated The stack should never be searched for the key. Use the version without the searchStack boolean instead.
-     */
-    @Deprecated
-    public static String getText(String key, String defaultMessage, List<Object> args, ValueStack stack, boolean searchStack) {
         String msg = null;
         TextProvider tp = null;
 
@@ -80,17 +59,11 @@
         }
 
         if (msg == null) {
-            // evaluate the defaultMessage as an OGNL expression
-            if (searchStack)
-                msg = stack.findString(defaultMessage);
-            
-            if (msg == null) {
-                // use the defaultMessage literal value
-                msg = defaultMessage;
-                msg = StringEscapeUtils.escapeEcmaScript(msg);
-                msg = StringEscapeUtils.escapeHtml4(msg);
-                LOG.debug("Message for key '{}' is null, returns escaped default message [{}]", key, msg);
-            }
+            // use the defaultMessage literal value
+            msg = defaultMessage;
+            msg = StringEscapeUtils.escapeHtml4(msg);
+            msg = StringEscapeUtils.escapeEcmaScript(msg);
+            LOG.debug("Message for key '{}' is null, returns escaped default message [{}]", key, msg);
 
             if (LOG.isWarnEnabled()) {
                 if (tp != null) {
diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties
index 1bbe20c..c61c5d4 100644
--- a/core/src/main/resources/org/apache/struts2/default.properties
+++ b/core/src/main/resources/org/apache/struts2/default.properties
@@ -19,7 +19,7 @@
 ### START SNIPPET: complete_file
 
 ### Struts default properties
-###(can be overridden by a struts.properties file in the root of the classpath)
+### (can be overridden by a struts.properties file in the root of the classpath)
 ###
 
 ### This can be used to set your default locale and encoding scheme
@@ -57,15 +57,15 @@
 ###       using generics. com.opensymphony.xwork2.util.GenericsObjectTypeDeterminer was deprecated since XWork 2, it's
 ###       functions are integrated in DefaultObjectTypeDeterminer now.
 ###       To disable tiger support use the "notiger" property value here.
-#struts.objectTypeDeterminer = tiger
-#struts.objectTypeDeterminer = notiger
+# struts.objectTypeDeterminer = tiger
+# struts.objectTypeDeterminer = notiger
 
 ### Parser to handle HTTP POST requests, encoded using the MIME-type multipart/form-data
 # struts.multipart.parser=cos
 # struts.multipart.parser=pell
 # struts.multipart.parser=jakarta-stream
 struts.multipart.parser=jakarta
-# uses javax.servlet.context.tempdir by default
+### Uses javax.servlet.context.tempdir by default
 struts.multipart.saveDir=
 struts.multipart.maxSize=2097152
 
@@ -74,7 +74,7 @@
 
 ### How request URLs are mapped to and from actions
 ### Vy default 'struts' name is used which maps to DefaultActionMapper
-#struts.mapper.class=restful
+# struts.mapper.class=restful
 
 ### Used by the DefaultActionMapper
 ### You may provide a comma separated list, e.g. struts.action.extension=action,jnlp,do
@@ -136,7 +136,7 @@
 
 ### when set to true, resource bundles will be reloaded on _every_ request.
 ### this is good during development, but should never be used in production
-### struts.i18n.reload=false
+# struts.i18n.reload=false
 
 ### Standard UI theme
 ### Change this to reflect which path should be used for JSP control tag templates by default
@@ -144,12 +144,12 @@
 struts.ui.templateDir=template
 ### Change this to use a different token to indicate template theme expansion
 struts.ui.theme.expansion.token=~~~
-#sets the default template type. Either ftl, vm, or jsp
+### Sets the default template type. Either ftl, vm, or jsp
 struts.ui.templateSuffix=ftl
 
 ### Configuration reloading
 ### This will cause the configuration to reload struts.xml when it is changed
-### struts.configuration.xml.reload=false
+# struts.configuration.xml.reload=false
 
 ### Location of velocity.properties file.  defaults to velocity.properties
 struts.velocity.configfile = velocity.properties
@@ -169,6 +169,10 @@
 ### Load custom default resource bundles
 # struts.custom.i18n.resources=testmessages,testmessages2
 
+### Control whether to search the default resource bundes for messages first (if true) or not (if false).
+### Default is false (when not set).
+# struts.i18n.search.defaultbundles.first=false
+
 ### workaround for some app servers that don't handle HttpServletRequest.getParameterMap()
 ### often used for WebLogic, Orion, and OC4J
 struts.dispatcher.parametersWorkaround = false
@@ -176,11 +180,11 @@
 ### configure the Freemarker Manager class to be used
 ### Allows user to plug-in customised Freemarker Manager if necessary
 ### MUST extends off org.apache.struts2.views.freemarker.FreemarkerManager
-#struts.freemarker.manager.classname=org.apache.struts2.views.freemarker.FreemarkerManager
+# struts.freemarker.manager.classname=org.apache.struts2.views.freemarker.FreemarkerManager
 
 ### Enables caching of FreeMarker templates
 ### Has the same effect as copying the templates under WEB_APP/templates
-### struts.freemarker.templatesCache=false
+# struts.freemarker.templatesCache=false
 
 ### Enables caching of models on the BeanWrapper
 struts.freemarker.beanwrapperCache=false
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java
index 542efda..66c716c 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java
@@ -83,23 +83,4 @@
         assertEquals(3, vs.findValue("strings.size()"));
     }
 
-    public void testDeprecatedAutoGrowCollectionLimit() {
-        PropertyAccessor accessor = container.getInstance(PropertyAccessor.class, ArrayList.class.getName());
-        ((XWorkListPropertyAccessor) accessor).setDeprecatedAutoGrowCollectionLimit("2");
-
-        List<String> myList = new ArrayList<>();
-        ListHolder listHolder = new ListHolder();
-        listHolder.setStrings(myList);
-
-        ValueStack vs = ActionContext.getContext().getValueStack();
-        vs.push(listHolder);
-
-        vs.setValue("strings[0]", "a");
-        vs.setValue("strings[1]", "b");
-        vs.setValue("strings[2]", "c");
-        vs.setValue("strings[3]", "d");
-
-        assertEquals(3, vs.findValue("strings.size()"));
-    }
-
 }
diff --git a/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
index 2a3038f..f7669b1 100644
--- a/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
@@ -371,6 +371,196 @@
             2, testStrutsLocalizedTextProvider.currentBundlesMapSize());
     }
 
+    /**
+     * Test the {@link StrutsLocalizedTextProvider#searchDefaultBundlesFirst} flag behaviour for basic correctness.
+     */
+    public void testSetSearchDefaultBundlesFirst() {
+        TestStrutsLocalizedTextProvider testStrutsLocalizedTextProvider = new TestStrutsLocalizedTextProvider();
+        assertFalse("Default setSearchDefaultBundlesFirst state is not false ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst(Boolean.TRUE.toString());
+        assertTrue("The setSearchDefaultBundlesFirst state is not true after explicit set ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst(Boolean.FALSE.toString());
+        assertFalse("The setSearchDefaultBundlesFirst state is not false after explicit set ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst("invalidstring");
+        assertFalse("The setSearchDefaultBundlesFirst state is not false after set with invalid value ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+    }
+
+    /**
+     * Test the {@link StrutsLocalizedTextProvider#getDefaultMessageWithAlternateKey(java.lang.String, java.lang.String, java.util.Locale, com.opensymphony.xwork2.util.ValueStack, java.lang.Object[], java.lang.String)}
+     * method for basic correctness.
+     */
+    public void testGetDefaultMessageWithAlternateKey() {
+        final String DEFAULT_MESSAGE = "This is the default message.";
+        final String DEFAULT_MESSAGE_WITH_PARAMS = DEFAULT_MESSAGE + "  We provide a couple of parameter placeholders: -{0}- and -{1}- for fun.";
+        final String param1 = "param1_String";
+        final String param2 = "param2_String";
+        final String[] paramArray = { param1, param2 };
+        TestStrutsLocalizedTextProvider testStrutsLocalizedTextProvider = new TestStrutsLocalizedTextProvider();
+
+        // Load some specific default bundles already provided and used by other tests within this module.
+        testStrutsLocalizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
+        testStrutsLocalizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/Bar");
+        testStrutsLocalizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/FindMe");
+
+        // Perform some standard checks on message retrieval using null or nonexistent keys and various default message combinations.
+        ValueStack valueStack = ActionContext.getContext().getValueStack();
+        AbstractLocalizedTextProvider.GetDefaultMessageReturnArg getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey(null, null, Locale.ENGLISH, valueStack, null, null);
+        assertNull("GetDefaultMessageReturnArg result not null with null keys and null default message ?", getDefaultMessageReturnArg);
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("key_does_not_exist", "alternateKey_does_not_exist", Locale.ENGLISH, valueStack, null, null);
+        assertNull("GetDefaultMessageReturnArg result not null with nonexistent keys and null default message ?", getDefaultMessageReturnArg);
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("key_does_not_exist", "alternateKey_does_not_exist", Locale.ENGLISH, valueStack, null, DEFAULT_MESSAGE);
+        assertNotNull("GetDefaultMessageReturnArg result is null with nonexistent keys and non-null default message ?", getDefaultMessageReturnArg);
+        assertFalse("GetDefaultMessageReturnArg result with nonexistent keys indicates message found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertEquals("GetDefaultMessageReturnArg result with nonexistent keys indicates message found in bundle ?", DEFAULT_MESSAGE, getDefaultMessageReturnArg.message);
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("key_does_not_exist", "alternateKey_does_not_exist", Locale.ENGLISH, valueStack, paramArray, DEFAULT_MESSAGE_WITH_PARAMS);
+        assertNotNull("GetDefaultMessageReturnArg result is null with nonexistent keys and non-null default message ?", getDefaultMessageReturnArg);
+        assertFalse("GetDefaultMessageReturnArg result with nonexistent keys indicates message found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertNotNull("GetDefaultMessageReturnArg result message is null ?", getDefaultMessageReturnArg.message);
+        assertTrue("GetDefaultMessageReturnArg result message does not contain deafult message ?", getDefaultMessageReturnArg.message.contains(DEFAULT_MESSAGE));
+        assertTrue("GetDefaultMessageReturnArg result message does not contain param1 ?", getDefaultMessageReturnArg.message.contains(param1));
+        assertTrue("GetDefaultMessageReturnArg result message does not contain param2 ?", getDefaultMessageReturnArg.message.contains(param2));
+
+        // Perform some checks where the initial key is null or does not exist in the default bundles, but the alternate key does.
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey(null, "username", Locale.ENGLISH, valueStack, paramArray, null);
+        assertNotNull("GetDefaultMessageReturnArg result is null with alternate key that exists ?", getDefaultMessageReturnArg);
+        assertTrue("GetDefaultMessageReturnArg result with alternate key that exists indicates message not found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertTrue("GetDefaultMessageReturnArg result with alternate key that exists indicates message is null or empty ?", (getDefaultMessageReturnArg.message != null && !getDefaultMessageReturnArg.message.isEmpty()));
+        assertEquals("GetDefaultMessageReturnArg result with alternate key 'username' not as expected ?", "Santa", getDefaultMessageReturnArg.message);
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("key_does_not_exist", "invalid.fieldvalue.title", Locale.ENGLISH, valueStack, paramArray, null);
+        assertNotNull("GetDefaultMessageReturnArg result is null with alternate key that exists ?", getDefaultMessageReturnArg);
+        assertTrue("GetDefaultMessageReturnArg result with alternate key that exists indicates message not found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertTrue("GetDefaultMessageReturnArg result with alternate key that exists indicates message is null or empty ?", (getDefaultMessageReturnArg.message != null && !getDefaultMessageReturnArg.message.isEmpty()));
+        assertEquals("GetDefaultMessageReturnArg result with alternate key 'invalid.fieldvalue.title' not as expected ?", "Title is invalid!", getDefaultMessageReturnArg.message);
+
+        // Perform some checks where the initial key exists, but the alternate key is null or nonexistent.
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("username", null, Locale.ENGLISH, valueStack, paramArray, null);
+        assertNotNull("GetDefaultMessageReturnArg result is null with key that exists ?", getDefaultMessageReturnArg);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message not found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message is null or empty ?", (getDefaultMessageReturnArg.message != null && !getDefaultMessageReturnArg.message.isEmpty()));
+        assertEquals("GetDefaultMessageReturnArg result with key 'username' not as expected ?", "Santa", getDefaultMessageReturnArg.message);
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("invalid.fieldvalue.title", "key_does_not_exist", Locale.ENGLISH, valueStack, paramArray, null);
+        assertNotNull("GetDefaultMessageReturnArg result is null with key that exists ?", getDefaultMessageReturnArg);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message not found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message is null or empty ?", (getDefaultMessageReturnArg.message != null && !getDefaultMessageReturnArg.message.isEmpty()));
+        assertEquals("GetDefaultMessageReturnArg result with key 'invalid.fieldvalue.title' not as expected ?", "Title is invalid!", getDefaultMessageReturnArg.message);
+
+        // Perform some checks where the initial key exists, and the alternate key exists.  The result found for the initial key should be returned (not the alternate).
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("username", "invalid.fieldvalue.title", Locale.ENGLISH, valueStack, paramArray, null);
+        assertNotNull("GetDefaultMessageReturnArg result is null with key that exists ?", getDefaultMessageReturnArg);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message not found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message is null or empty ?", (getDefaultMessageReturnArg.message != null && !getDefaultMessageReturnArg.message.isEmpty()));
+        assertEquals("GetDefaultMessageReturnArg result with key 'username' not as expected ?", "Santa", getDefaultMessageReturnArg.message);
+        getDefaultMessageReturnArg = testStrutsLocalizedTextProvider.getDefaultMessageWithAlternateKey("invalid.fieldvalue.title", "username", Locale.ENGLISH, valueStack, paramArray, null);
+        assertNotNull("GetDefaultMessageReturnArg result is null with key that exists ?", getDefaultMessageReturnArg);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message not found in bundle ?", getDefaultMessageReturnArg.foundInBundle);
+        assertTrue("GetDefaultMessageReturnArg result with key that exists indicates message is null or empty ?", (getDefaultMessageReturnArg.message != null && !getDefaultMessageReturnArg.message.isEmpty()));
+        assertEquals("GetDefaultMessageReturnArg result with key 'invalid.fieldvalue.title' not as expected ?", "Title is invalid!", getDefaultMessageReturnArg.message);
+    }
+
+    /**
+     * Test the {@link StrutsLocalizedTextProvider#findText(java.lang.Class, java.lang.String, java.util.Locale, java.lang.String, java.lang.Object[], com.opensymphony.xwork2.util.ValueStack) }
+     * method for basic correctness.
+     * 
+     * It is the version of the method that will search the class hierarchy resource bundles first, unless {@link StrutsLocalizedTextProvider#searchDefaultBundlesFirst}
+     * is true (in which case it will search the default resource bundles first).  No matter the flag setting, it should search until it finds a match, or fails to find
+     * a match and returns the default message parameter that was passed.
+     */
+    public void testFindText_FullParameterSet_FirstParameterIsClass() {
+        final String DEFAULT_MESSAGE = "This is the default message.";
+        final String INDEXED_COLLECTION_ONLYGENERALFORM_EXISTS = "title.indexed[20]";  // Only title.indexed[*] exists.
+        final String EXISTS_IN_DEFAULT_AND_CLASS_BUNDLES = "compare.sameproperty.differentbundles";  // Exists in LocalizedTextUtilTest properties (default bundles), and Bar properties (class bundles only).
+        final String DEFAULT_MESSAGE_WITH_PARAMS = DEFAULT_MESSAGE + "  We provide a couple of parameter placeholders: -{0}- and -{1}- for fun.";
+        final String param1 = "param1_String";
+        final String param2 = "param2_String";
+        final String[] paramArray = { param1, param2 };
+        TestStrutsLocalizedTextProvider testStrutsLocalizedTextProvider = new TestStrutsLocalizedTextProvider();
+
+        // Load some specific default bundles already provided and used by other tests within this module.
+        // Note: Intentionally not including the Bar properties file as a default bundle so that we can test retrievals of items that are only available via the class
+        //       or the default bundles.
+        testStrutsLocalizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
+        testStrutsLocalizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/FindMe");
+
+        // Perform some standard checks on message retrieval both for correctness checks and code coverage (such as the NONEXISTENT_INDEXED_COLLECTION,
+        // which exercises the indexed name logic in findText())
+        ValueStack valueStack = ActionContext.getContext().getValueStack();
+        Bar bar = new Bar();
+        assertFalse("Initial setSearchDefaultBundlesFirst state is not false ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        String messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "title", Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Bar class title property lookup result does not match expectations (missing or different) ?", "Title:", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), INDEXED_COLLECTION_ONLYGENERALFORM_EXISTS, Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Bar class general indexed collection lookup result does not match expectations (missing or different) ?", "Indexed title text for test!", messageResult);
+
+        // Test lookup with search default bundles first set true.  For properties that exist only with the class bundle, there should be no change.
+        // Repeat the tests with properties only in the class bundle.
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst(Boolean.TRUE.toString());
+        assertTrue("Updated setSearchDefaultBundlesFirst state is not true ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "title", Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Bar class title property lookup result does not match expectations (missing or different) ?", "Title:", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), INDEXED_COLLECTION_ONLYGENERALFORM_EXISTS, Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Bar class general indexed collection lookup result does not match expectations (missing or different) ?", "Indexed title text for test!", messageResult);
+
+        // Test with a property that is in both the class bundle and default bundles, with search default bundles first true.
+        // The property match from the default bundles should be returned.
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst(Boolean.TRUE.toString());
+        assertTrue("Updated setSearchDefaultBundlesFirst state is not true ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), EXISTS_IN_DEFAULT_AND_CLASS_BUNDLES, Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Result is not the property from the default bundles ?", "This is the value in the LocalizedTextUtilTest properties!", messageResult);
+
+        // Test with a property that is in both the class bundle and default bundles, with search default bundles first false.
+        // The property match from the Bar class bundle should be returned.
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst(Boolean.FALSE.toString());
+        assertFalse("Updated setSearchDefaultBundlesFirst state is not false ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), EXISTS_IN_DEFAULT_AND_CLASS_BUNDLES, Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Result is not the property from the Bar bundle ?", "This is the value in the Bar properties!", messageResult);
+
+        // Test with some different properties (including null and nonexistent ones), with search default bundles first false.
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst(Boolean.FALSE.toString());
+        assertFalse("Updated setSearchDefaultBundlesFirst state is not false ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), null, Locale.ENGLISH, null, paramArray, valueStack);
+        assertNull("Result with null key and null default message is not null ?", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "key_does_not_exist", Locale.ENGLISH, null, paramArray, valueStack);
+        assertNull("Result with nonexistent key and null default message is not null ?", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), null, Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Result with null key and non-null default message is not the default message ?", DEFAULT_MESSAGE, messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "key_does_not_exist", Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Result with nonexistent key and non-null default message is not the default message ?", DEFAULT_MESSAGE, messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "key_does_not_exist", Locale.ENGLISH, DEFAULT_MESSAGE_WITH_PARAMS, paramArray, valueStack);
+        assertNotNull("Result with nonexistent key and non-null default message is null ?", messageResult);
+        assertTrue("Result with parameterized default message does not contain deafult message ?", messageResult.contains(DEFAULT_MESSAGE));
+        assertTrue("Result with parameterized default message does not contain param1 ?", messageResult.contains(param1));
+        assertTrue("Result with parameterized default message does not contain param2 ?", messageResult.contains(param2));
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "username", Locale.ENGLISH, null, paramArray, valueStack);
+        assertEquals("Result of username lookup not as expected ?", "Santa", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "bean.name", Locale.ENGLISH, null, paramArray, valueStack);
+        assertEquals("Result of bean.name lookup not as expected ?", "Haha you cant FindMe!", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "bean2.name", Locale.ENGLISH, null, paramArray, valueStack);
+        assertEquals("Result of bean2.name lookup not as expected ?", "Okay! You found Me!", messageResult);
+
+        // Test with some different properties (including null and nonexistent ones), with search default bundles first true.
+        testStrutsLocalizedTextProvider.setSearchDefaultBundlesFirst(Boolean.TRUE.toString());
+        assertTrue("Updated setSearchDefaultBundlesFirst state is not true ?", testStrutsLocalizedTextProvider.searchDefaultBundlesFirst);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), null, Locale.ENGLISH, null, paramArray, valueStack);
+        assertNull("Result with null key and null default message is not null ?", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "key_does_not_exist", Locale.ENGLISH, null, paramArray, valueStack);
+        assertNull("Result with nonexistent key and null default message is not null ?", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), null, Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Result with null key and non-null default message is not the default message ?", DEFAULT_MESSAGE, messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "key_does_not_exist", Locale.ENGLISH, DEFAULT_MESSAGE, paramArray, valueStack);
+        assertEquals("Result with nonexistent key and non-null default message is not the default message ?", DEFAULT_MESSAGE, messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "key_does_not_exist", Locale.ENGLISH, DEFAULT_MESSAGE_WITH_PARAMS, paramArray, valueStack);
+        assertNotNull("Result with nonexistent key and non-null default message is null ?", messageResult);
+        assertTrue("Result with parameterized default message does not contain deafult message ?", messageResult.contains(DEFAULT_MESSAGE));
+        assertTrue("Result with parameterized default message does not contain param1 ?", messageResult.contains(param1));
+        assertTrue("Result with parameterized default message does not contain param2 ?", messageResult.contains(param2));
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "username", Locale.ENGLISH, null, paramArray, valueStack);
+        assertEquals("Result of username lookup not as expected ?", "Santa", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "bean.name", Locale.ENGLISH, null, paramArray, valueStack);
+        assertEquals("Result of bean.name lookup not as expected ?", "Haha you cant FindMe!", messageResult);
+        messageResult = testStrutsLocalizedTextProvider.findText(bar.getClass(), "bean2.name", Locale.ENGLISH, null, paramArray, valueStack);
+        assertEquals("Result of bean2.name lookup not as expected ?", "Okay! You found Me!", messageResult);
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
diff --git a/core/src/test/resources/com/opensymphony/xwork2/util/Bar.properties b/core/src/test/resources/com/opensymphony/xwork2/util/Bar.properties
index 75eeb3b..99a1f1a 100644
--- a/core/src/test/resources/com/opensymphony/xwork2/util/Bar.properties
+++ b/core/src/test/resources/com/opensymphony/xwork2/util/Bar.properties
@@ -18,3 +18,5 @@
 #
 title=Title:
 invalid.fieldvalue.title=Title is invalid!
+title.indexed[*]=Indexed title text for test!
+compare.sameproperty.differentbundles=This is the value in the Bar properties!
diff --git a/core/src/test/resources/com/opensymphony/xwork2/util/LocalizedTextUtilTest.properties b/core/src/test/resources/com/opensymphony/xwork2/util/LocalizedTextUtilTest.properties
index 1475796..16cb9e6 100644
--- a/core/src/test/resources/com/opensymphony/xwork2/util/LocalizedTextUtilTest.properties
+++ b/core/src/test/resources/com/opensymphony/xwork2/util/LocalizedTextUtilTest.properties
@@ -19,3 +19,4 @@
 test.format.date={0,date,short}
 xw377=xw377
 username=Santa
+compare.sameproperty.differentbundles=This is the value in the LocalizedTextUtilTest properties!
diff --git a/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java b/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java
index 72272cb..31a2766 100644
--- a/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java
+++ b/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiObjectFactory.java
@@ -45,7 +45,7 @@
  */
 public class CdiObjectFactory extends ObjectFactory {
 
-    private static final Logger LOG = LogManager.getLogger(CdiObjectFactory.class);
+	private static final Logger LOG = LogManager.getLogger(CdiObjectFactory.class);
 
     /**
      * The key under which the BeanManager can be found according to CDI API docs
@@ -60,10 +60,11 @@
 	 */
 	public static final String CDI_JNDIKEY_BEANMANAGER_COMP_ENV = "java:comp/env/BeanManager";
 
+	public static final String STRUTS_OBJECT_FACTORY_CDI_JNDI_KEY = "struts.objectFactory.cdi.jndiKey";
 
 	private String jndiKey;
 
-	@Inject(value = "struts.objectFactory.cdi.jndiKey", required = false)
+	@Inject(value = STRUTS_OBJECT_FACTORY_CDI_JNDI_KEY, required = false)
 	public void setJndiKey( String jndiKey ) {
 		this.jndiKey = jndiKey;
 	}
diff --git a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/JavaTemplateConstants.java b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/JavaTemplateConstants.java
new file mode 100644
index 0000000..8001c83
--- /dev/null
+++ b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/JavaTemplateConstants.java
@@ -0,0 +1,27 @@
+/*
+ * 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 org.apache.struts2.views.java;
+
+public class JavaTemplateConstants {
+
+    public static final String STRUTS_JAVATEMPLATES_DEFAULT_TEMPLATE_TYPE = "struts.javatemplates.defaultTemplateType";
+
+    public static final String STRUTS_JAVATEMPLATES_CUSTOM_THEMES = "struts.javatemplates.customThemes";
+
+}
diff --git a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/JavaTemplateEngine.java b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/JavaTemplateEngine.java
index 67bb9b3..dea448c 100644
--- a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/JavaTemplateEngine.java
+++ b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/JavaTemplateEngine.java
@@ -110,7 +110,7 @@
      *
      * @param themeClasses a comma delimited list of custom theme class names
      */
-    @Inject(value = "struts.javatemplates.customThemes", required = false)
+    @Inject(value = JavaTemplateConstants.STRUTS_JAVATEMPLATES_CUSTOM_THEMES, required = false)
     public void setThemeClasses(String themeClasses) {
         StringTokenizer customThemes = new StringTokenizer(themeClasses, ",");
         while (customThemes.hasMoreTokens()) {
@@ -135,7 +135,7 @@
      *
      * @param defaultTemplateTheme the struts default theme
      */
-    @Inject(value = "struts.javatemplates.defaultTemplateType", required = false)
+    @Inject(value = JavaTemplateConstants.STRUTS_JAVATEMPLATES_DEFAULT_TEMPLATE_TYPE, required = false)
     public void setDefaultTemplateType(String defaultTemplateTheme) {
         // Make sure we don't set ourself as default for race condition
         if (defaultTemplateTheme != null && !defaultTemplateTheme.equalsIgnoreCase(getSuffix())) {
diff --git a/plugins/jfreechart/pom.xml b/plugins/jfreechart/pom.xml
index f2d4d21..0ee1990 100644
--- a/plugins/jfreechart/pom.xml
+++ b/plugins/jfreechart/pom.xml
@@ -35,7 +35,7 @@
         <dependency>
             <groupId>org.jfree</groupId>
             <artifactId>jcommon</artifactId>
-            <version>1.0.23</version>
+            <version>1.0.24</version>
             <scope>provided</scope>
             <exclusions>
                 <exclusion>
@@ -47,7 +47,7 @@
         <dependency>
             <groupId>org.jfree</groupId>
             <artifactId>jfreechart</artifactId>
-            <version>1.0.19</version>
+            <version>1.5.1</version>
             <scope>provided</scope>
             <exclusions>
                 <exclusion>
diff --git a/plugins/jfreechart/src/main/java/org/apache/struts2/dispatcher/ChartResult.java b/plugins/jfreechart/src/main/java/org/apache/struts2/dispatcher/ChartResult.java
index eabd988..c7c16a3 100644
--- a/plugins/jfreechart/src/main/java/org/apache/struts2/dispatcher/ChartResult.java
+++ b/plugins/jfreechart/src/main/java/org/apache/struts2/dispatcher/ChartResult.java
@@ -18,12 +18,13 @@
  */
 package org.apache.struts2.dispatcher;
 
+import com.opensymphony.xwork2.config.ConfigurationException;
 import org.apache.struts2.ServletActionContext;
 import com.opensymphony.xwork2.ActionInvocation;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
 import org.apache.struts2.result.StrutsResultSupport;
-import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.ChartUtils;
 import org.jfree.chart.JFreeChart;
 
 import java.io.OutputStream;
@@ -105,14 +106,16 @@
     private final static Logger LOG = LogManager.getLogger(ChartResult.class);
 
     private static final long serialVersionUID = -6484761870055986612L;
+
     private static final String DEFAULT_TYPE = "png";
     private static final String DEFAULT_VALUE = "chart";
 
     private JFreeChart chart; // the JFreeChart to render
     private boolean chartSet;
-    String height, width;
-    String type = DEFAULT_TYPE; // supported are jpg, jpeg or png, defaults to png
-    String value = DEFAULT_VALUE; // defaults to 'chart'
+    private String height;
+    private String width;
+    private String type = DEFAULT_TYPE; // supported are jpg, jpeg or png, defaults to png
+    private String value = DEFAULT_VALUE; // defaults to 'chart'
 
     // CONSTRUCTORS ----------------------------
 
@@ -200,11 +203,11 @@
             // check the type to see what kind of output we have to produce
             if ("png".equalsIgnoreCase(type)) {
                 response.setContentType("image/png");
-                ChartUtilities.writeChartAsPNG(os, chart, getIntValueFromString(width), getIntValueFromString(height));
+                ChartUtils.writeChartAsPNG(os, chart, getIntValueFromString(width), getIntValueFromString(height));
             }
             else if ("jpg".equalsIgnoreCase(type) || "jpeg".equalsIgnoreCase(type)) {
                 response.setContentType("image/jpg");
-                ChartUtilities.writeChartAsJPEG(os, chart, getIntValueFromString(width), getIntValueFromString(height));
+                ChartUtils.writeChartAsJPEG(os, chart, getIntValueFromString(width), getIntValueFromString(height));
             }
             else
                 throw new IllegalArgumentException(type + " is not a supported render type (only JPG and PNG are).");
@@ -217,9 +220,8 @@
      * Sets up result properties, parsing etc.
      *
      * @param invocation Current invocation.
-     * @throws Exception on initialization error.
      */
-    private void initializeProperties(ActionInvocation invocation) throws Exception {
+    private void initializeProperties(ActionInvocation invocation) {
 
         if (height != null) {
             height = conditionalParse(height, invocation);
@@ -238,12 +240,12 @@
         }
     }
 
-    private Integer getIntValueFromString(String value) {
+    private int getIntValueFromString(String value) {
         try {
             return Integer.parseInt(value);
         } catch (Exception e) {
             LOG.error("Specified value for width or height is not of type Integer...", e);
-            return null;
+            throw new ConfigurationException("Wrong value \"" + value + "\", expected Integer!", e);
         }
     }
 
diff --git a/plugins/oval/src/main/java/org/apache/struts2/oval/interceptor/OValValidationInterceptor.java b/plugins/oval/src/main/java/org/apache/struts2/oval/interceptor/OValValidationInterceptor.java
index e6cf603..58f1fd2 100644
--- a/plugins/oval/src/main/java/org/apache/struts2/oval/interceptor/OValValidationInterceptor.java
+++ b/plugins/oval/src/main/java/org/apache/struts2/oval/interceptor/OValValidationInterceptor.java
@@ -55,6 +55,9 @@
  This interceptor provides validation using the OVal validation framework
  */
 public class OValValidationInterceptor extends MethodFilterInterceptor {
+
+    public static final String STRUTS_OVAL_VALIDATE_JPAANNOTATIONS = "struts.oval.validateJPAAnnotations";
+
     private static final Logger LOG = LogManager.getLogger(OValValidationInterceptor.class);
 
     protected final static String VALIDATE_PREFIX = "validate";
@@ -85,7 +88,7 @@
     /**
      * Enable OVal support for JPA
      */
-    @Inject(value = "struts.oval.validateJPAAnnotations")
+    @Inject(value = STRUTS_OVAL_VALIDATE_JPAANNOTATIONS)
     public void setValidateJPAAnnotations(String validateJPAAnnotations) {
         this.validateJPAAnnotations = Boolean.parseBoolean(validateJPAAnnotations);
     }