From 026c992c083e21382121780760d48fd8ab20a6d5 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 2 Nov 2024 23:53:02 +0100 Subject: [PATCH] WW-5481 Extracts duplicated code --- .../text/AbstractLocalizedTextProvider.java | 131 ++++------ .../text/GlobalLocalizedTextProvider.java | 170 ++----------- .../struts2/text/LocalizedTextProvider.java | 164 +++++++++++- .../text/StrutsLocalizedTextProvider.java | 234 +++--------------- 4 files changed, 253 insertions(+), 446 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java index e7683cd6c5..6185552c1d 100644 --- a/core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java @@ -67,24 +67,18 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { private final Set missingBundles = ConcurrentHashMap.newKeySet(); private final ConcurrentMap 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 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 @@ public void setCustomI18NResources(String bundles) { } } - /** - * 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 localList = getCurrentBundleNames(); for (String bundleName : localList) { @@ -130,7 +116,7 @@ public String findDefaultText(String aTextName, Locale locale) { 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 @@ public String findDefaultText(String aTextName, Locale locale) { } 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 @@ public String findDefaultText(String aTextName, Locale locale, Object[] params) return null; } - - /** - *

- * Finds a localized text message for the given key, aTextName, in the specified resource - * bundle. - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - *

- * If a message is not found a WARN log will be logged. - *

- * - * @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 @@ public void setSearchDefaultBundlesFirst(String searchDefaultBundlesFirst) { this.searchDefaultBundlesFirst = Boolean.parseBoolean(searchDefaultBundlesFirst); } - /** - * Finds the given resource bundle by it's name. - *

- * Will use Thread.currentThread().getContextClassLoader() as the classloader. - *

- * - * @param aBundleName the name of the bundle (usually it's FQN classname). - * @param locale the locale. - * @return the bundle, null 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 @@ public ResourceBundle findResourceBundle(String aBundleName, Locale locale) { 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 @@ public ResourceBundle findResourceBundle(String aBundleName, Locale locale) { 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 @@ protected String findMessage(Class clazz, String key, String indexedKey, Loca 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; diff --git a/core/src/main/java/org/apache/struts2/text/GlobalLocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/text/GlobalLocalizedTextProvider.java index 33ab693dd1..31ea216503 100644 --- a/core/src/main/java/org/apache/struts2/text/GlobalLocalizedTextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/GlobalLocalizedTextProvider.java @@ -41,182 +41,42 @@ public GlobalLocalizedTextProvider() { 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]); } - /** - *

- * 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: - *

- * - *
    - *
  1. Look for the message in the default resource bundles.
  2. - *
  3. If not found, return defaultMessage
  4. - *
- * - *

- * 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[*]). - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - * @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); } - /** - *

- * 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: - *

- * - *
    - *
  1. Look for the message in the default resource bundles.
  2. - *
  3. If not found, return defaultMessage
  4. - *
- * - *

- * 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[*]). - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - *

- * If a message is not found a DEBUG level log warning will be logged. - *

- * - * @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; } - /** - *

- * Finds a localized text message for the given key, aTextName, in the specified resource bundle - * with aTextName as the default message. - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - * @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]); } - /** - *

- * Finds a localized text message for the given key, aTextName, in the specified resource - * bundle. - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - *

- * If a message is not found a WARN log will be logged. - *

- * - * @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); } } diff --git a/core/src/main/java/org/apache/struts2/text/LocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/text/LocalizedTextProvider.java index fcaf399750..472ab35f44 100644 --- a/core/src/main/java/org/apache/struts2/text/LocalizedTextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/LocalizedTextProvider.java @@ -26,24 +26,168 @@ 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. + *

+ * Will use Thread.currentThread().getContextClassLoader() as the classloader. + *

+ * + * @param bundleName the name of the bundle (usually it's FQN classname). + * @param locale the locale. + * @return the bundle, null 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: + * + *
    + *
  1. Look for the message in the default resource bundles.
  2. + *
  3. If not found, return defaultMessage
  4. + *
+ *

+ * 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[*]). + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * 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: + * + *

    + *
  1. Look for the message in the default resource bundles.
  2. + *
  3. If not found, return defaultMessage
  4. + *
+ *

+ * 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[*]). + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + *

+ * If a message is not 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. + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * 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. + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + *

+ * If a message is not 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. + *

+ * If a message is found, it will also be interpolated. Anything within ${...} + * will be treated as an OGNL expression and evaluated as such. + *

+ * If a message is not 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); } diff --git a/core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java b/core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java index 3199d8a0f1..bfdfe22fb4 100644 --- a/core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java +++ b/core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java @@ -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.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 StrutsLocalizedTextProvider() { 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]); } - /** - *

- * 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: - *

- * - *
    - *
  1. If {@link #searchDefaultBundlesFirst} is true, look for the message in the default resource bundles first.
  2. - *
  3. Look for message in aClass' class hierarchy. - *
      - *
    1. Look for the message in a resource bundle for aClass
    2. - *
    3. If not found, look for the message in a resource bundle for any implemented interface
    4. - *
    5. If not found, traverse up the Class' hierarchy and repeat from the first sub-step
    6. - *
  4. - *
  5. 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).
  6. - *
  7. 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 - * user.address.state, 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.
  8. - *
  9. If not found, look for the message in aClass' package hierarchy.
  10. - *
  11. If still not found, look for the message in the default resource bundles - * (Note: the lookup is not repeated again if {@link #searchDefaultBundlesFirst} was true).
  12. - *
  13. Return defaultMessage
  14. - *
- * - *

- * 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[*]). - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - * @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); } - /** - *

- * 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: - *

- * - *
    - *
  1. If {@link #searchDefaultBundlesFirst} is true, look for the message in the default resource bundles first.
  2. - *
  3. Look for message in aClass' class hierarchy. - *
      - *
    1. Look for the message in a resource bundle for aClass
    2. - *
    3. If not found, look for the message in a resource bundle for any implemented interface
    4. - *
    5. If not found, traverse up the Class' hierarchy and repeat from the first sub-step
    6. - *
  4. - *
  5. 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).
  6. - *
  7. 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 - * user.address.state, 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.
  8. - *
  9. If not found, look for the message in aClass' package hierarchy.
  10. - *
  11. If still not found, look for the message in the default resource bundles - * (Note: the lookup is not repeated again if {@link #searchDefaultBundlesFirst} was true).
  12. - *
  13. Return defaultMessage
  14. - *
- * - *

- * 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[*]). - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - *

- * If a message is not found a DEBUG level log warning will be logged. - *

- * - * @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 String findText(Class aClass, String aTextName, Locale locale, String // 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 String findText(Class aClass, String aTextName, Locale locale, String 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 String findText(Class aClass, String aTextName, Locale locale, String } // 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 String findText(Class aClass, String aTextName, Locale locale, String 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 String findText(Class aClass, String aTextName, Locale locale, String } // 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 String findText(Class aClass, String aTextName, Locale locale, String // 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; } - /** - *

- * Finds a localized text message for the given key, aTextName, in the specified resource bundle - * with aTextName as the default message. - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - * @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]); } - /** - *

- * Finds a localized text message for the given key, aTextName, in the specified resource - * bundle. - *

- * - *

- * If a message is found, it will also be interpolated. Anything within ${...} - * will be treated as an OGNL expression and evaluated as such. - *

- * - *

- * If a message is not found a WARN log will be logged. - *

- * - * @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; } + }