Merge pull request #464 from apache/WW-5000-constants
[WW-5000] Extracts string literals as constants
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 d1cf8fc..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";
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/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!