WW-5481 Extracts duplicated code

This commit is contained in:
Lukasz Lenart
2024-11-02 23:53:02 +01:00
parent 80dab5294b
commit e6c7ced601
4 changed files with 253 additions and 446 deletions
@@ -67,24 +67,18 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
private final Set<String> missingBundles = ConcurrentHashMap.newKeySet();
private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>();
/**
* 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.
*/
@Override
public void addDefaultResourceBundle(String resourceBundleName) {
public void addDefaultResourceBundle(String bundleName) {
//make sure this doesn't get added more than once
final ClassLoader ccl = getCurrentThreadContextClassLoader();
synchronized (XWORK_MESSAGES_BUNDLE) {
List<String> bundles = classLoaderMap.computeIfAbsent(ccl.hashCode(), k -> new CopyOnWriteArrayList<>());
bundles.remove(resourceBundleName);
bundles.add(0, resourceBundleName);
bundles.remove(bundleName);
bundles.add(0, bundleName);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", resourceBundleName, ccl.toString());
LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", bundleName, ccl.toString());
}
}
@@ -113,16 +107,8 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
}
}
/**
* Returns a localized message for the specified key, aTextName. Neither the key nor the
* message is evaluated.
*
* @param aTextName the message key
* @param locale the locale the message should be for
* @return a localized message based on the specified key, or null if no localized message can be found for it
*/
@Override
public String findDefaultText(String aTextName, Locale locale) {
public String findDefaultText(String textKey, Locale locale) {
List<String> localList = getCurrentBundleNames();
for (String bundleName : localList) {
@@ -130,7 +116,7 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
if (bundle != null) {
reloadBundles();
try {
return bundle.getString(aTextName);
return bundle.getString(textKey);
} catch (MissingResourceException e) {
// will be logged when not found in any bundle
}
@@ -138,26 +124,17 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
}
if (devMode) {
LOG.warn("Missing key [{}] in bundles [{}]!", aTextName, localList);
LOG.warn("Missing key [{}] in bundles [{}]!", textKey, localList);
} else {
LOG.debug("Missing key [{}] in bundles [{}]!", aTextName, localList);
LOG.debug("Missing key [{}] in bundles [{}]!", textKey, localList);
}
return null;
}
/**
* Returns a localized message for the specified key, aTextName, substituting variables from the
* array of params into the message. Neither the key nor the message is evaluated.
*
* @param aTextName the message key
* @param locale the locale the message should be for
* @param params an array of objects to be substituted into the message text
* @return A formatted message based on the specified key, or null if no localized message can be found for it
*/
@Override
public String findDefaultText(String aTextName, Locale locale, Object[] params) {
String defaultText = findDefaultText(aTextName, locale);
public String findDefaultText(String textKey, Locale locale, Object[] params) {
String defaultText = findDefaultText(textKey, locale);
if (defaultText != null) {
MessageFormat mf = buildMessageFormat(defaultText, locale);
return formatWithNullDetection(mf, params);
@@ -165,51 +142,27 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
return null;
}
/**
* <p>
* Finds a localized text message for the given key, aTextName, in the specified resource
* bundle.
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* <p>
* If a message is <b>not</b> found a WARN log will be logged.
* </p>
*
* @param bundle the bundle
* @param aTextName the key
* @param locale the locale
* @param defaultMessage the default message to use if no message was found in the bundle
* @param args arguments for the message formatter.
* @param valueStack the OGNL value stack.
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
@Override
public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
public String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args,
ValueStack valueStack) {
try {
reloadBundles(valueStack.getContext());
String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
String message = TextParseUtil.translateVariables(bundle.getString(textKey), valueStack);
MessageFormat mf = buildMessageFormat(message, locale);
return formatWithNullDetection(mf, args);
} catch (MissingResourceException ex) {
if (devMode) {
LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
LOG.warn("Missing key [{}] in bundle [{}]!", textKey, bundle);
} else {
LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
LOG.debug("Missing key [{}] in bundle [{}]!", textKey, bundle);
}
}
GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
GetDefaultMessageReturnArg result = getDefaultMessage(textKey, locale, valueStack, args, defaultMessage);
if (unableToFindTextForKey(result)) {
LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", textKey, locale);
}
return result != null ? result.message : null;
}
@@ -425,20 +378,10 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
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.
* </p>
*
* @param aBundleName the name of the bundle (usually it's FQN classname).
* @param locale the locale.
* @return the bundle, <tt>null</tt> if not found.
*/
@Override
public ResourceBundle findResourceBundle(String aBundleName, Locale locale) {
public ResourceBundle findResourceBundle(String bundleName, Locale locale) {
ClassLoader classLoader = getCurrentThreadContextClassLoader();
String key = createMissesKey(String.valueOf(classLoader.hashCode()), aBundleName, locale);
String key = createMissesKey(String.valueOf(classLoader.hashCode()), bundleName, locale);
if (missingBundles.contains(key)) {
return null;
@@ -449,7 +392,7 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
if (bundlesMap.containsKey(key)) {
bundle = bundlesMap.get(key);
} else {
bundle = ResourceBundle.getBundle(aBundleName, locale, classLoader);
bundle = ResourceBundle.getBundle(bundleName, locale, classLoader);
bundlesMap.putIfAbsent(key, bundle);
}
} catch (MissingResourceException ex) {
@@ -458,15 +401,15 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
if (bundlesMap.containsKey(key)) {
bundle = bundlesMap.get(key);
} else {
bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode()));
bundle = ResourceBundle.getBundle(bundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode()));
bundlesMap.putIfAbsent(key, bundle);
}
} catch (MissingResourceException e) {
LOG.debug("Missing resource bundle [{}]!", aBundleName, e);
LOG.debug("Missing resource bundle [{}]!", bundleName, e);
missingBundles.add(key);
}
} else {
LOG.debug("Missing resource bundle [{}]!", aBundleName);
LOG.debug("Missing resource bundle [{}]!", bundleName);
missingBundles.add(key);
}
}
@@ -656,6 +599,36 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
return null;
}
protected String extractIndexedName(String textKey) {
String indexedTextName = null;
// calculate indexedTextName (collection[*]) if applicable
if (textKey.contains("[")) {
int i = -1;
indexedTextName = textKey;
while ((i = indexedTextName.indexOf('[', i + 1)) != -1) {
int j = indexedTextName.indexOf(']', i);
String a = indexedTextName.substring(0, i);
String b = indexedTextName.substring(j);
indexedTextName = a + "[*" + b;
}
}
return indexedTextName;
}
protected void logMissingText(Class<?> startClazz, String textKey, Locale locale, GetDefaultMessageReturnArg result, String indexedTextName) {
// could we find the text, if not log a WARN
if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
String warn = "Unable to find text for key '" + textKey + "' ";
if (indexedTextName != null) {
warn += " or indexed key '" + indexedTextName + "' ";
}
warn += "in class '" + startClazz.getName() + "' and locale '" + locale + "'";
LOG.debug(warn);
}
}
static class MessageFormatKey {
String pattern;
Locale locale;
@@ -41,182 +41,42 @@ public class GlobalLocalizedTextProvider extends AbstractLocalizedTextProvider {
addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
}
/**
* Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
* with aTextName as the default message.
*
* @param aClass class name
* @param aTextName text name
* @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
*/
@Override
public String findText(Class<?> aClass, String aTextName, Locale locale) {
return findText(aClass, aTextName, locale, aTextName, new Object[0]);
public String findText(Class<?> startClazz, String textKey, Locale locale) {
return findText(startClazz, textKey, locale, textKey, new Object[0]);
}
/**
* <p>
* Finds a localized text message for the given key, aTextName. Both the key and the message
* itself is evaluated as required. The following algorithm is used to find the requested
* message:
* </p>
*
* <ol>
* <li>Look for the message in the default resource bundles.</li>
* <li>If not found, return defaultMessage</li>
* </ol>
*
* <p>
* When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
* message for that specific key cannot be found, the general form will also be looked up
* (i.e. user.phone[*]).
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* @param aClass the class whose name to use as the start point for the search
* @param aTextName the key to find the text message for
* @param locale the locale the message should be for
* @param defaultMessage the message to be returned if no text message can be found in any
* resource bundle
* @param args arguments
* resource bundle
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
@Override
public String findText(Class<?> aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
public String findText(Class<?> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args) {
ValueStack valueStack = ActionContext.getContext().getValueStack();
return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
return findText(startClazz, textKey, locale, defaultMessage, args, valueStack);
}
/**
* <p>
* Finds a localized text message for the given key, aTextName. Both the key and the message
* itself is evaluated as required. The following algorithm is used to find the requested
* message:
* </p>
*
* <ol>
* <li>Look for the message in the default resource bundles.</li>
* <li>If not found, return defaultMessage</li>
* </ol>
*
* <p>
* When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
* message for that specific key cannot be found, the general form will also be looked up
* (i.e. user.phone[*]).
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* <p>
* If a message is <b>not</b> found a DEBUG level log warning will be logged.
* </p>
*
* @param aClass the class whose name to use as the start point for the search
* @param aTextName the key to find the text message for
* @param locale the locale the message should be for
* @param defaultMessage the message to be returned if no text message can be found in any
* resource bundle
* @param args arguments
* @param valueStack the value stack to use to evaluate expressions instead of the
* one in the ActionContext ThreadLocal
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
@Override
public String findText(Class<?> aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) {
String indexedTextName = null;
if (aTextName == null) {
LOG.warn("Trying to find text with null key!");
aTextName = "";
}
// calculate indexedTextName (collection[*]) if applicable
if (aTextName.contains("[")) {
int i = -1;
indexedTextName = aTextName;
while ((i = indexedTextName.indexOf('[', i + 1)) != -1) {
int j = indexedTextName.indexOf(']', i);
String a = indexedTextName.substring(0, i);
String b = indexedTextName.substring(j);
indexedTextName = a + "[*" + b;
}
public String findText(Class<?> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) {
if (textKey == null) {
LOG.debug("Key is null, short-circuit to default message");
return defaultMessage;
}
String indexedTextName = extractIndexedName(textKey);
// get default
GetDefaultMessageReturnArg result = getDefaultMessageWithAlternateKey(aTextName, indexedTextName, locale, valueStack, args, defaultMessage);
GetDefaultMessageReturnArg result = getDefaultMessageWithAlternateKey(textKey, indexedTextName, locale, valueStack, args, defaultMessage);
// could we find the text, if not log a warn
if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
String warn = "Unable to find text for key '" + aTextName + "' ";
if (indexedTextName != null) {
warn += " or indexed key '" + indexedTextName + "' ";
}
warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
LOG.debug(warn);
}
logMissingText(startClazz, textKey, locale, result, indexedTextName);
return result != null ? result.message : null;
}
/**
* <p>
* Finds a localized text message for the given key, aTextName, in the specified resource bundle
* with aTextName as the default message.
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* @param bundle a resource bundle name
* @param aTextName text name
* @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(ResourceBundle, String, Locale, String, Object[])
*/
@Override
public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
return findText(bundle, aTextName, locale, aTextName, new Object[0]);
public String findText(ResourceBundle bundle, String textKey, Locale locale) {
return findText(bundle, textKey, locale, textKey, new Object[0]);
}
/**
* <p>
* Finds a localized text message for the given key, aTextName, in the specified resource
* bundle.
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* <p>
* If a message is <b>not</b> found a WARN log will be logged.
* </p>
*
* @param bundle the bundle
* @param aTextName the key
* @param locale the locale
* @param defaultMessage the default message to use if no message was found in the bundle
* @param args arguments for the message formatter.
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
@Override
public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
public String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args) {
ValueStack valueStack = ActionContext.getContext().getValueStack();
return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
return findText(bundle, textKey, locale, defaultMessage, args, valueStack);
}
}
@@ -26,24 +26,168 @@ import java.util.ResourceBundle;
public interface LocalizedTextProvider extends Serializable {
String findDefaultText(String aTextName, Locale locale);
/**
* Returns a localized message for the specified key, aTextName. Neither the key nor the
* message is evaluated.
*
* @param textKey the message key
* @param locale the locale the message should be for
* @return a localized message based on the specified key, or null if no localized message can be found for it
*/
String findDefaultText(String textKey, Locale locale);
String findDefaultText(String aTextName, Locale locale, Object[] params);
/**
* Returns a localized message for the specified key, aTextName, substituting variables from the
* array of params into the message. Neither the key nor the message is evaluated.
*
* @param textKey the message key
* @param locale the locale the message should be for
* @param params an array of objects to be substituted into the message text
* @return A formatted message based on the specified key, or null if no localized message can be found for it
*/
String findDefaultText(String textKey, Locale locale, Object[] params);
ResourceBundle findResourceBundle(String aBundleName, Locale locale);
/**
* Finds the given resource bundle by it's name.
* <p>
* Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
* </p>
*
* @param bundleName the name of the bundle (usually it's FQN classname).
* @param locale the locale.
* @return the bundle, <tt>null</tt> if not found.
*/
ResourceBundle findResourceBundle(String bundleName, Locale locale);
String findText(Class<?> aClass, String aTextName, Locale locale);
/**
* Calls {@link #findText(Class startClazz, String textKey, Locale locale, String defaultMessage, Object[] args)}
* with textKey as the default message.
*
* @param startClazz class name
* @param textKey text name
* @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(Class startClazz, String textKey, Locale locale, String defaultMessage, Object[] args)
*/
String findText(Class<?> startClazz, String textKey, Locale locale);
String findText(Class<?> aClass, String aTextName, Locale locale, String defaultMessage, Object[] args);
/**
* Finds a localized text message for the given key, textKey. Both the key and the message
* itself is evaluated as required. The following algorithm is used to find the requested
* message:
*
* <ol>
* <li>Look for the message in the default resource bundles.</li>
* <li>If not found, return defaultMessage</li>
* </ol>
* <p>
* When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
* message for that specific key cannot be found, the general form will also be looked up
* (i.e. user.phone[*]).
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
*
* @param startClazz the class whose name to use as the start point for the search
* @param textKey the key to find the text message for
* @param locale the locale the message should be for
* @param defaultMessage the message to be returned if no text message can be found in any
* resource bundle
* @param args arguments
* resource bundle
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
String findText(Class<?> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args);
String findText(Class<?> aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack);
/**
* Finds a localized text message for the given key, textKey. Both the key and the message
* itself is evaluated as required. The following algorithm is used to find the requested
* message:
*
* <ol>
* <li>Look for the message in the default resource bundles.</li>
* <li>If not found, return defaultMessage</li>
* </ol>
* <p>
* When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
* message for that specific key cannot be found, the general form will also be looked up
* (i.e. user.phone[*]).
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* <p>
* If a message is <b>not</b> found a DEBUG level log warning will be logged.
*
* @param startClazz the class whose name to use as the start point for the search
* @param textKey the key to find the text message for
* @param locale the locale the message should be for
* @param defaultMessage the message to be returned if no text message can be found in any
* resource bundle
* @param args arguments
* @param valueStack the value stack to use to evaluate expressions instead of the
* one in the ActionContext ThreadLocal
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
String findText(Class<?> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack);
String findText(ResourceBundle bundle, String aTextName, Locale locale);
/**
* Finds a localized text message for the given key, aTextName, in the specified resource bundle
* with aTextName as the default message.
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
*
* @param bundle a resource bundle name
* @param textKey text name
* @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(ResourceBundle, String, Locale, String, Object[])
*/
String findText(ResourceBundle bundle, String textKey, Locale locale);
String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args);
/**
* Finds a localized text message for the given key, aTextName, in the specified resource
* bundle.
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* <p>
* If a message is <b>not</b> found a WARN log will be logged.
*
* @param bundle the bundle
* @param textKey the key
* @param locale the locale
* @param defaultMessage the default message to use if no message was found in the bundle
* @param args arguments for the message formatter.
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args);
String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack);
/**
* Finds a localized text message for the given key, aTextName, in the specified resource
* bundle.
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* <p>
* If a message is <b>not</b> found a WARN log will be logged.
*
* @param bundle the bundle
* @param textKey the key
* @param locale the locale
* @param defaultMessage the default message to use if no message was found in the bundle
* @param args arguments for the message formatter.
* @param valueStack the OGNL value stack.
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack);
void addDefaultResourceBundle(String resourceBundleName);
/**
* Adds the bundle to the internal list of default bundles.
* If the bundle already exists in the list it will be re-added.
*
* @param bundleName the name of the bundle to add.
*/
void addDefaultResourceBundle(String bundleName);
}
@@ -18,6 +18,8 @@
*/
package org.apache.struts2.text;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.ModelDriven;
@@ -25,8 +27,6 @@ import org.apache.struts2.conversion.impl.XWorkConverter;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.reflection.ReflectionProvider;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.beans.PropertyDescriptor;
import java.util.Locale;
@@ -46,150 +46,26 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
}
/**
* Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
* with aTextName as the default message.
*
* @param aClass class name
* @param aTextName text name
* @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
*/
@Override
public String findText(Class<?> aClass, String aTextName, Locale locale) {
return findText(aClass, aTextName, locale, aTextName, new Object[0]);
public String findText(Class<?> startClazz, String textKey, Locale locale) {
return findText(startClazz, textKey, locale, textKey, new Object[0]);
}
/**
* <p>
* Finds a localized text message for the given key, aTextName. Both the key and the message
* itself is evaluated as required. The following algorithm is used to find the requested
* message:
* </p>
*
* <ol>
* <li>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>
* <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 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
* (Note: the lookup is not repeated again if {@link #searchDefaultBundlesFirst} was <code>true</code>).</li>
* <li>Return defaultMessage</li>
* </ol>
*
* <p>
* When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
* message for that specific key cannot be found, the general form will also be looked up
* (i.e. user.phone[*]).
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* @param aClass the class whose name to use as the start point for the search
* @param aTextName the key to find the text message for
* @param locale the locale the message should be for
* @param defaultMessage the message to be returned if no text message can be found in any
* resource bundle
* @param args arguments
* resource bundle
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
@Override
public String findText(Class<?> aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
public String findText(Class<?> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args) {
ValueStack valueStack = ActionContext.getContext().getValueStack();
return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
return findText(startClazz, textKey, locale, defaultMessage, args, valueStack);
}
/**
* <p>
* Finds a localized text message for the given key, aTextName. Both the key and the message
* itself is evaluated as required. The following algorithm is used to find the requested
* message:
* </p>
*
* <ol>
* <li>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>
* <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 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
* (Note: the lookup is not repeated again if {@link #searchDefaultBundlesFirst} was <code>true</code>).</li>
* <li>Return defaultMessage</li>
* </ol>
*
* <p>
* When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
* message for that specific key cannot be found, the general form will also be looked up
* (i.e. user.phone[*]).
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* <p>
* If a message is <b>not</b> found a DEBUG level log warning will be logged.
* </p>
*
* @param aClass the class whose name to use as the start point for the search
* @param aTextName the key to find the text message for
* @param locale the locale the message should be for
* @param defaultMessage the message to be returned if no text message can be found in any
* resource bundle
* @param args arguments
* @param valueStack the value stack to use to evaluate expressions instead of the
* one in the ActionContext ThreadLocal
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
@Override
public String findText(Class<?> aClass, String aTextName, Locale locale, String defaultMessage, Object[] args,
public String findText(Class<?> startClazz, String textKey, Locale locale, String defaultMessage, Object[] args,
ValueStack valueStack) {
String indexedTextName = null;
if (aTextName == null) {
LOG.warn("Trying to find text with null key!");
aTextName = "";
}
// calculate indexedTextName (collection[*]) if applicable
if (aTextName.contains("[")) {
int i = -1;
indexedTextName = aTextName;
while ((i = indexedTextName.indexOf('[', i + 1)) != -1) {
int j = indexedTextName.indexOf(']', i);
String a = indexedTextName.substring(0, i);
String b = indexedTextName.substring(j);
indexedTextName = a + "[*" + b;
}
if (textKey == null) {
LOG.debug("Key is null, short-circuit to default message");
return defaultMessage;
}
String indexedTextName = extractIndexedName(textKey);
// 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.
@@ -198,21 +74,21 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
// If search default bundles first is set true, call alternative logic first.
if (searchDefaultBundlesFirst) {
result = getDefaultMessageWithAlternateKey(aTextName, indexedTextName, locale, valueStack, args, defaultMessage);
result = getDefaultMessageWithAlternateKey(textKey, 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.
return result.message; // Found a message in the default resource bundles for textKey or indexedTextName.
}
}
// search up class hierarchy
String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
String msg = findMessage(startClazz, textKey, indexedTextName, locale, args, null, valueStack);
if (msg != null) {
return msg;
}
if (ModelDriven.class.isAssignableFrom(aClass)) {
if (ModelDriven.class.isAssignableFrom(startClazz)) {
ActionContext context = ActionContext.getContext();
// search up model's class hierarchy
ActionInvocation actionInvocation = context.getActionInvocation();
@@ -223,7 +99,7 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
if (action instanceof ModelDriven) {
Object model = ((ModelDriven<?>) action).getModel();
if (model != null) {
msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack);
msg = findMessage(model.getClass(), textKey, indexedTextName, locale, args, null, valueStack);
if (msg != null) {
return msg;
}
@@ -233,7 +109,7 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
}
// nothing still? alright, search the package hierarchy now
for (Class<?> clazz = aClass;
for (Class<?> clazz = startClazz;
(clazz != null) && !clazz.equals(Object.class);
clazz = clazz.getSuperclass()) {
@@ -241,7 +117,7 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
while (basePackageName.lastIndexOf('.') != -1) {
basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.'));
String packageName = basePackageName + ".package";
msg = getMessage(packageName, locale, aTextName, valueStack, args);
msg = getMessage(packageName, locale, textKey, valueStack, args);
if (msg != null) {
return msg;
@@ -258,22 +134,22 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
}
// see if it's a child property
int idx = aTextName.indexOf('.');
int idx = textKey.indexOf('.');
if (idx != -1) {
String newKey = null;
String prop = null;
if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
idx = aTextName.indexOf('.', XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
if (textKey.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
idx = textKey.indexOf('.', XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
if (idx != -1) {
prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1);
prop = textKey.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + textKey.substring(idx + 1);
}
} else {
prop = aTextName.substring(0, idx);
newKey = aTextName.substring(idx + 1);
prop = textKey.substring(0, idx);
newKey = textKey.substring(idx + 1);
}
if (prop != null) {
@@ -310,74 +186,28 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
// 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);
result = getDefaultMessageWithAlternateKey(textKey, indexedTextName, locale, valueStack, args, defaultMessage);
}
// could we find the text, if not log a warn
if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
String warn = "Unable to find text for key '" + aTextName + "' ";
if (indexedTextName != null) {
warn += " or indexed key '" + indexedTextName + "' ";
}
warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
LOG.debug(warn);
}
logMissingText(startClazz, textKey, locale, result, indexedTextName);
return result != null ? result.message : null;
}
/**
* <p>
* Finds a localized text message for the given key, aTextName, in the specified resource bundle
* with aTextName as the default message.
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* @param bundle a resource bundle name
* @param aTextName text name
* @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
*/
@Override
public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
return findText(bundle, aTextName, locale, aTextName, new Object[0]);
public String findText(ResourceBundle bundle, String textKey, Locale locale) {
return findText(bundle, textKey, locale, textKey, new Object[0]);
}
/**
* <p>
* Finds a localized text message for the given key, aTextName, in the specified resource
* bundle.
* </p>
*
* <p>
* If a message is found, it will also be interpolated. Anything within <code>${...}</code>
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
* <p>
* If a message is <b>not</b> found a WARN log will be logged.
* </p>
*
* @param bundle the bundle
* @param aTextName the key
* @param locale the locale
* @param defaultMessage the default message to use if no message was found in the bundle
* @param args arguments for the message formatter.
* @return the localized text, or null if none can be found and no defaultMessage is provided
*/
@Override
public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
public String findText(ResourceBundle bundle, String textKey, Locale locale, String defaultMessage, Object[] args) {
ValueStack valueStack = ActionContext.getContext().getValueStack();
return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
return findText(bundle, textKey, locale, defaultMessage, args, valueStack);
}
@Inject
public void setReflectionProvider(ReflectionProvider reflectionProvider) {
this.reflectionProvider = reflectionProvider;
}
}