You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2017/05/18 08:09:43 UTC
[01/10] struts git commit: WW-4762 Drops unused code
Repository: struts
Updated Branches:
refs/heads/master 8b862f7cb -> cb0f42abc
WW-4762 Drops unused code
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/45d1a491
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/45d1a491
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/45d1a491
Branch: refs/heads/master
Commit: 45d1a491311a82f6b2404d839e7e18269a26192f
Parents: b46a6e7
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 11:55:49 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 11:55:49 2017 +0200
----------------------------------------------------------------------
.../org/apache/struts2/util/StrutsTestCaseHelper.java | 11 -----------
1 file changed, 11 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/45d1a491/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java b/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
index 9c449f5..f8994d1 100644
--- a/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
+++ b/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
@@ -23,7 +23,6 @@ package org.apache.struts2.util;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Container;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import org.apache.struts2.dispatcher.Dispatcher;
@@ -40,16 +39,6 @@ import java.util.Map;
*/
public class StrutsTestCaseHelper {
- /**
- * Sets up the configuration settings, XWork configuration, and
- * message resources
- *
- * @throws Exception in case of any error
- */
- public static void setUp() throws Exception {
- DefaultLocalizedTextProvider.clearDefaultResourceBundles();
- }
-
public static Dispatcher initDispatcher(ServletContext ctx, Map<String,String> params) {
if (params == null) {
params = new HashMap<>();
[08/10] struts git commit: WW-4762 Extracts common method
Posted by lu...@apache.org.
WW-4762 Extracts common method
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/fd168c42
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/fd168c42
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/fd168c42
Branch: refs/heads/master
Commit: fd168c423f70383a6240cba9809a87b661ef39de
Parents: 7360b5e
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed May 10 14:12:39 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed May 10 14:12:39 2017 +0200
----------------------------------------------------------------------
.../util/AbstractLocalizedTextProvider.java | 59 ++++++++++++++++++++
.../util/DefaultLocalizedTextProvider.java | 48 ----------------
.../util/StrutsLocalizedTextProvider.java | 58 -------------------
3 files changed, 59 insertions(+), 106 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/fd168c42/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
index 1c11fec..fcef47e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -148,6 +148,55 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
return null;
}
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName, in the specified resource
+ * bundle.
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * <p>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ * </p>
+ *
+ * @param bundle the bundle
+ * @param aTextName the key
+ * @param locale the locale
+ * @param defaultMessage the default message to use if no message was found in the bundle
+ * @param args arguments for the message formatter.
+ * @param valueStack the OGNL value stack.
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
+ ValueStack valueStack) {
+ try {
+ reloadBundles(valueStack.getContext());
+
+ String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
+ MessageFormat mf = buildMessageFormat(message, locale);
+
+ return formatWithNullDetection(mf, args);
+ } catch (MissingResourceException ex) {
+ if (devMode) {
+ LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+ } else {
+ LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+ }
+ }
+
+ GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+ if (unableToFindTextForKey(result)) {
+ LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
+ }
+ return result != null ? result.message : null;
+ }
+
/**
* @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader
*/
@@ -320,6 +369,16 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
}
return bundle;
}
+
+ /**
+ * Clears all the internal lists.
+ *
+ * @deprecated used only in tests
+ */
+ @Deprecated
+ public void reset() {
+ // no-op
+ }
/**
* Determines if we found the text in the bundles.
http://git-wip-us.apache.org/repos/asf/struts/blob/fd168c42/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
index acd3943..a709180 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
@@ -259,52 +259,4 @@ public class DefaultLocalizedTextProvider extends AbstractLocalizedTextProvider
return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
}
- /**
- * <p>
- * Finds a localized text message for the given key, aTextName, in the specified resource
- * bundle.
- * </p>
- *
- * <p>
- * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
- * will be treated as an OGNL expression and evaluated as such.
- * </p>
- *
- * <p>
- * If a message is <b>not</b> found a WARN log will be logged.
- * </p>
- *
- * @param bundle the bundle
- * @param aTextName the key
- * @param locale the locale
- * @param defaultMessage the default message to use if no message was found in the bundle
- * @param args arguments for the message formatter.
- * @param valueStack the OGNL value stack.
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- */
- @Override
- public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
- ValueStack valueStack) {
- try {
- reloadBundles(valueStack.getContext());
-
- String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
- MessageFormat mf = buildMessageFormat(message, locale);
-
- return formatWithNullDetection(mf, args);
- } catch (MissingResourceException ex) {
- if (devMode) {
- LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
- } else {
- LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
- }
- }
-
- GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
- if (unableToFindTextForKey(result)) {
- LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
- }
- return result != null ? result.message : null;
- }
-
}
http://git-wip-us.apache.org/repos/asf/struts/blob/fd168c42/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
index 0b97279..a31c4ae 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -419,62 +419,4 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
}
- /**
- * <p>
- * Finds a localized text message for the given key, aTextName, in the specified resource
- * bundle.
- * </p>
- *
- * <p>
- * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
- * will be treated as an OGNL expression and evaluated as such.
- * </p>
- *
- * <p>
- * If a message is <b>not</b> found a WARN log will be logged.
- * </p>
- *
- * @param bundle the bundle
- * @param aTextName the key
- * @param locale the locale
- * @param defaultMessage the default message to use if no message was found in the bundle
- * @param args arguments for the message formatter.
- * @param valueStack the OGNL value stack.
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- */
- @Override
- public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
- ValueStack valueStack) {
- try {
- reloadBundles(valueStack.getContext());
-
- String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
- MessageFormat mf = buildMessageFormat(message, locale);
-
- return formatWithNullDetection(mf, args);
- } catch (MissingResourceException ex) {
- if (devMode) {
- LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
- } else {
- LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
- }
- }
-
- GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
- if (unableToFindTextForKey(result)) {
- LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
- }
- return result != null ? result.message : null;
- }
-
- /**
- * Clears all the internal lists.
- *
- * @deprecated used only in tests
- */
- @Deprecated
- public void reset() {
- // no-op
- }
-
}
[10/10] struts git commit: WW-4762 Adds additiona
LocalizedTextProvider which uses only default bundles
Posted by lu...@apache.org.
WW-4762 Adds additiona LocalizedTextProvider which uses only default bundles
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/cb0f42ab
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/cb0f42ab
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/cb0f42ab
Branch: refs/heads/master
Commit: cb0f42abccc884b2243fd9b9aac6a63f109e1af4
Parents: 8b862f7 4604333
Author: Lukasz Lenart <lu...@apache.org>
Authored: Thu May 18 10:09:33 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Thu May 18 10:09:33 2017 +0200
----------------------------------------------------------------------
.../xwork2/DefaultTextProvider.java | 8 +-
.../config/impl/DefaultConfiguration.java | 2 +-
.../providers/XWorkConfigurationProvider.java | 4 +-
.../util/AbstractLocalizedTextProvider.java | 576 ++++++++++++++
.../util/DefaultLocalizedTextProvider.java | 764 +------------------
.../util/StrutsLocalizedTextProvider.java | 422 ++++++++++
.../apache/struts2/dispatcher/Dispatcher.java | 1 -
.../struts2/util/StrutsTestCaseHelper.java | 11 -
core/src/main/resources/struts-default.xml | 4 +-
.../util/DefaultLocalizedTextProviderTest.java | 267 -------
.../util/StrutsLocalizedTextProviderTest.java | 267 +++++++
.../PropertiesConfigurationProviderTest.java | 1 -
12 files changed, 1285 insertions(+), 1042 deletions(-)
----------------------------------------------------------------------
[07/10] struts git commit: WW-4762 Moves common method up
Posted by lu...@apache.org.
WW-4762 Moves common method up
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/7360b5e1
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/7360b5e1
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/7360b5e1
Branch: refs/heads/master
Commit: 7360b5e1956f415866c189d7adda861456d4d069
Parents: bb19a62
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed May 10 13:59:20 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed May 10 13:59:20 2017 +0200
----------------------------------------------------------------------
.../util/AbstractLocalizedTextProvider.java | 19 ++++++++++++++++++
.../util/StrutsLocalizedTextProvider.java | 21 --------------------
2 files changed, 19 insertions(+), 21 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/7360b5e1/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
index 8c377c1..1c11fec 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -6,6 +6,7 @@ import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.struts2.StrutsConstants;
import java.lang.reflect.Field;
@@ -20,6 +21,7 @@ import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
+import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -77,6 +79,23 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
return Thread.currentThread().getContextClassLoader();
}
+ @Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false)
+ public void setCustomI18NResources(String bundles) {
+ if (bundles != null && bundles.length() > 0) {
+ StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
+
+ while (customBundles.hasMoreTokens()) {
+ String name = customBundles.nextToken();
+ try {
+ LOG.trace("Loading global messages from [{}]", name);
+ addDefaultResourceBundle(name);
+ } catch (Exception e) {
+ LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
+ }
+ }
+ }
+ }
+
/**
* Returns a localized message for the specified key, aTextName. Neither the key nor the
* message is evaluated.
http://git-wip-us.apache.org/repos/asf/struts/blob/7360b5e1/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
index 9552869..0b97279 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -25,19 +25,15 @@ import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
-import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.apache.struts2.StrutsConstants;
import java.beans.PropertyDescriptor;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
-import java.util.StringTokenizer;
/**
* Provides support for localization in the framework, it can be used to read only default bundles,
@@ -62,23 +58,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
}
- @Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false)
- public void setCustomI18NResources(String bundles) {
- if (bundles != null && bundles.length() > 0) {
- StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
-
- while (customBundles.hasMoreTokens()) {
- String name = customBundles.nextToken();
- try {
- LOG.trace("Loading global messages from [{}]", name);
- addDefaultResourceBundle(name);
- } catch (Exception e) {
- LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
- }
- }
- }
- }
-
/**
* Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale
* with language "en", country "US" and variant "foo". This will parse the output of
[06/10] struts git commit: WW-4762 Introduces default provider which
only uses default bundles
Posted by lu...@apache.org.
WW-4762 Introduces default provider which only uses default bundles
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/bb19a620
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/bb19a620
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/bb19a620
Branch: refs/heads/master
Commit: bb19a6209886795b76385e48fd86424c144ee029
Parents: 2e23d7a
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 20:28:11 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 20:28:11 2017 +0200
----------------------------------------------------------------------
.../util/AbstractLocalizedTextProvider.java | 145 ++++++++-
.../util/DefaultLocalizedTextProvider.java | 310 +++++++++++++++++++
.../util/StrutsLocalizedTextProvider.java | 161 +---------
3 files changed, 461 insertions(+), 155 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/bb19a620/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
index a2578bc..8c377c1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -20,11 +20,12 @@ import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
-public abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
+abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
private static final Logger LOG = LogManager.getLogger(AbstractLocalizedTextProvider.class);
@@ -302,6 +303,26 @@ public abstract class AbstractLocalizedTextProvider implements LocalizedTextProv
}
/**
+ * Determines if we found the text in the bundles.
+ *
+ * @param result the result so far
+ * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
+ */
+ protected boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
+ if (result == null || result.message == null) {
+ return true;
+ }
+
+ // did we find it in the bundle, then no problem?
+ if (result.foundInBundle) {
+ return false;
+ }
+
+ // not found in bundle
+ return true;
+ }
+
+ /**
* Creates a key to used for lookup/storing in the bundle misses cache.
*
* @param prefix the prefix for the returning String - it is supposed to be the ClassLoader hash code.
@@ -313,6 +334,128 @@ public abstract class AbstractLocalizedTextProvider implements LocalizedTextProv
return prefix + aBundleName + "_" + locale.toString();
}
+ /**
+ * @return the default message.
+ */
+ protected GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
+ String defaultMessage) {
+ GetDefaultMessageReturnArg result = null;
+ boolean found = true;
+
+ if (key != null) {
+ String message = findDefaultText(key, locale);
+
+ if (message == null) {
+ message = defaultMessage;
+ found = false; // not found in bundles
+ }
+
+ // defaultMessage may be null
+ if (message != null) {
+ MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
+
+ String msg = formatWithNullDetection(mf, args);
+ result = new GetDefaultMessageReturnArg(msg, found);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * @return the message from the named resource bundle.
+ */
+ protected String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
+ ResourceBundle bundle = findResourceBundle(bundleName, locale);
+ if (bundle == null) {
+ return null;
+ }
+ if (valueStack != null)
+ reloadBundles(valueStack.getContext());
+ try {
+ String message = bundle.getString(key);
+ if (valueStack != null)
+ message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
+ MessageFormat mf = buildMessageFormat(message, locale);
+ return formatWithNullDetection(mf, args);
+ } catch (MissingResourceException e) {
+ if (devMode) {
+ LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
+ } else {
+ LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Traverse up class hierarchy looking for message. Looks at class, then implemented interface,
+ * before going up hierarchy.
+ *
+ * @return the message
+ */
+ protected String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
+ ValueStack valueStack) {
+ if (checked == null) {
+ checked = new TreeSet<>();
+ } else if (checked.contains(clazz.getName())) {
+ return null;
+ }
+
+ // look in properties of this class
+ String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedKey != null) {
+ msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+
+ // look in properties of implemented interfaces
+ Class[] interfaces = clazz.getInterfaces();
+
+ for (Class anInterface : interfaces) {
+ msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedKey != null) {
+ msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+
+ // traverse up hierarchy
+ if (clazz.isInterface()) {
+ interfaces = clazz.getInterfaces();
+
+ for (Class anInterface : interfaces) {
+ msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ } else {
+ if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
+ return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
+ }
+ }
+
+ return null;
+ }
+
static class MessageFormatKey {
String pattern;
Locale locale;
http://git-wip-us.apache.org/repos/asf/struts/blob/bb19a620/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
new file mode 100644
index 0000000..acd3943
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
@@ -0,0 +1,310 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ModelDriven;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Provides support for localization in the framework, it can be used to read only default bundles,
+ * or it can search the class hierarchy to find proper bundles.
+ */
+public class DefaultLocalizedTextProvider extends AbstractLocalizedTextProvider {
+
+ private static final Logger LOG = LogManager.getLogger(DefaultLocalizedTextProvider.class);
+
+ public DefaultLocalizedTextProvider() {
+ addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE);
+ addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
+ }
+
+ /**
+ * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
+ * with aTextName as the default message.
+ *
+ * @param aClass class name
+ * @param aTextName text name
+ * @param locale the locale
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
+ */
+ @Override
+ public String findText(Class aClass, String aTextName, Locale locale) {
+ return findText(aClass, aTextName, locale, aTextName, new Object[0]);
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName. Both the key and the message
+ * itself is evaluated as required. The following algorithm is used to find the requested
+ * message:
+ * </p>
+ *
+ * <ol>
+ * <li>Look for message in aClass' class hierarchy.
+ * <ol>
+ * <li>Look for the message in a resource bundle for aClass</li>
+ * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+ * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+ * </ol></li>
+ * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+ * the model's class hierarchy (repeat sub-steps listed above).</li>
+ * <li>If not found, look for message in child property. This is determined by evaluating
+ * the message key as an OGNL expression. For example, if the key is
+ * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+ * object. If so, repeat the entire process fromthe beginning with the object's class as
+ * aClass and "address.state" as the message key.</li>
+ * <li>If not found, look for the message in aClass' package hierarchy.</li>
+ * <li>If still not found, look for the message in the default resource bundles.</li>
+ * <li>Return defaultMessage</li>
+ * </ol>
+ *
+ * <p>
+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+ * message for that specific key cannot be found, the general form will also be looked up
+ * (i.e. user.phone[*]).
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * @param aClass the class whose name to use as the start point for the search
+ * @param aTextName the key to find the text message for
+ * @param locale the locale the message should be for
+ * @param defaultMessage the message to be returned if no text message can be found in any
+ * resource bundle
+ * @param args arguments
+ * resource bundle
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+ ValueStack valueStack = ActionContext.getContext().getValueStack();
+ return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
+
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName. Both the key and the message
+ * itself is evaluated as required. The following algorithm is used to find the requested
+ * message:
+ * </p>
+ *
+ * <ol>
+ * <li>Look for message in aClass' class hierarchy.
+ * <ol>
+ * <li>Look for the message in a resource bundle for aClass</li>
+ * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+ * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+ * </ol></li>
+ * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+ * the model's class hierarchy (repeat sub-steps listed above).</li>
+ * <li>If not found, look for message in child property. This is determined by evaluating
+ * the message key as an OGNL expression. For example, if the key is
+ * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+ * object. If so, repeat the entire process fromthe beginning with the object's class as
+ * aClass and "address.state" as the message key.</li>
+ * <li>If not found, look for the message in aClass' package hierarchy.</li>
+ * <li>If still not found, look for the message in the default resource bundles.</li>
+ * <li>Return defaultMessage</li>
+ * </ol>
+ *
+ * <p>
+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+ * message for that specific key cannot be found, the general form will also be looked up
+ * (i.e. user.phone[*]).
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * <p>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ * </p>
+ *
+ * @param aClass the class whose name to use as the start point for the search
+ * @param aTextName the key to find the text message for
+ * @param locale the locale the message should be for
+ * @param defaultMessage the message to be returned if no text message can be found in any
+ * resource bundle
+ * @param args arguments
+ * @param valueStack the value stack to use to evaluate expressions instead of the
+ * one in the ActionContext ThreadLocal
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) {
+ String indexedTextName = null;
+ if (aTextName == null) {
+ LOG.warn("Trying to find text with null key!");
+ aTextName = "";
+ }
+ // calculate indexedTextName (collection[*]) if applicable
+ if (aTextName.contains("[")) {
+ int i = -1;
+
+ indexedTextName = aTextName;
+
+ while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
+ int j = indexedTextName.indexOf("]", i);
+ String a = indexedTextName.substring(0, i);
+ String b = indexedTextName.substring(j);
+ indexedTextName = a + "[*" + b;
+ }
+ }
+
+ // get default
+ GetDefaultMessageReturnArg result;
+ if (indexedTextName == null) {
+ result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+ } else {
+ result = getDefaultMessage(aTextName, locale, valueStack, args, null);
+ if (result != null && result.message != null) {
+ return result.message;
+ }
+ result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
+ }
+
+ // could we find the text, if not log a warn
+ if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
+ String warn = "Unable to find text for key '" + aTextName + "' ";
+ if (indexedTextName != null) {
+ warn += " or indexed key '" + indexedTextName + "' ";
+ }
+ warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
+ LOG.debug(warn);
+ }
+
+ return result != null ? result.message : null;
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName, in the specified resource bundle
+ * with aTextName as the default message.
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * @param bundle a resource bundle name
+ * @param aTextName text name
+ * @param locale the locale
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ * @see #findText(ResourceBundle, String, Locale, String, Object[])
+ */
+ @Override
+ public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
+ return findText(bundle, aTextName, locale, aTextName, new Object[0]);
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName, in the specified resource
+ * bundle.
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * <p>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ * </p>
+ *
+ * @param bundle the bundle
+ * @param aTextName the key
+ * @param locale the locale
+ * @param defaultMessage the default message to use if no message was found in the bundle
+ * @param args arguments for the message formatter.
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+ ValueStack valueStack = ActionContext.getContext().getValueStack();
+ return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName, in the specified resource
+ * bundle.
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * <p>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ * </p>
+ *
+ * @param bundle the bundle
+ * @param aTextName the key
+ * @param locale the locale
+ * @param defaultMessage the default message to use if no message was found in the bundle
+ * @param args arguments for the message formatter.
+ * @param valueStack the OGNL value stack.
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
+ ValueStack valueStack) {
+ try {
+ reloadBundles(valueStack.getContext());
+
+ String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
+ MessageFormat mf = buildMessageFormat(message, locale);
+
+ return formatWithNullDetection(mf, args);
+ } catch (MissingResourceException ex) {
+ if (devMode) {
+ LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+ } else {
+ LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+ }
+ }
+
+ GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+ if (unableToFindTextForKey(result)) {
+ LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
+ }
+ return result != null ? result.message : null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/bb19a620/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
index 3d03f9a..9552869 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -37,9 +37,7 @@ import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
-import java.util.Set;
import java.util.StringTokenizer;
-import java.util.TreeSet;
/**
* Provides support for localization in the framework, it can be used to read only default bundles,
@@ -89,7 +87,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
* @param localeStr The locale String to parse.
* @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
* @return requested Locale
- *
* @deprecated please use {@link org.apache.commons.lang3.LocaleUtils#toLocale(String)}
*/
@Deprecated
@@ -130,10 +127,9 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
* Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
* with aTextName as the default message.
*
- * @param aClass class name
- * @param aTextName text name
- * @param locale the locale
- *
+ * @param aClass class name
+ * @param aTextName text name
+ * @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
*/
@@ -251,7 +247,7 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
ValueStack valueStack) {
String indexedTextName = null;
if (aTextName == null) {
- LOG.warn("Trying to find text with null key!");
+ LOG.warn("Trying to find text with null key!");
aTextName = "";
}
// calculate indexedTextName (collection[*]) if applicable
@@ -395,26 +391,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
}
/**
- * Determines if we found the text in the bundles.
- *
- * @param result the result so far
- * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
- */
- private boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
- if (result == null || result.message == null) {
- return true;
- }
-
- // did we find it in the bundle, then no problem?
- if (result.foundInBundle) {
- return false;
- }
-
- // not found in bundle
- return true;
- }
-
- /**
* <p>
* Finds a localized text message for the given key, aTextName, in the specified resource bundle
* with aTextName as the default message.
@@ -425,10 +401,9 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
* will be treated as an OGNL expression and evaluated as such.
* </p>
*
- * @param bundle a resource bundle name
- * @param aTextName text name
- * @param locale the locale
- *
+ * @param bundle a resource bundle name
+ * @param aTextName text name
+ * @param locale the locale
* @return the localized text, or null if none can be found and no defaultMessage is provided
* @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
*/
@@ -514,128 +489,6 @@ public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
}
/**
- * @return the default message.
- */
- private GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
- String defaultMessage) {
- GetDefaultMessageReturnArg result = null;
- boolean found = true;
-
- if (key != null) {
- String message = findDefaultText(key, locale);
-
- if (message == null) {
- message = defaultMessage;
- found = false; // not found in bundles
- }
-
- // defaultMessage may be null
- if (message != null) {
- MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
-
- String msg = formatWithNullDetection(mf, args);
- result = new GetDefaultMessageReturnArg(msg, found);
- }
- }
-
- return result;
- }
-
- /**
- * @return the message from the named resource bundle.
- */
- private String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
- ResourceBundle bundle = findResourceBundle(bundleName, locale);
- if (bundle == null) {
- return null;
- }
- if (valueStack != null)
- reloadBundles(valueStack.getContext());
- try {
- String message = bundle.getString(key);
- if (valueStack != null)
- message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
- MessageFormat mf = buildMessageFormat(message, locale);
- return formatWithNullDetection(mf, args);
- } catch (MissingResourceException e) {
- if (devMode) {
- LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
- } else {
- LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
- }
- return null;
- }
- }
-
- /**
- * Traverse up class hierarchy looking for message. Looks at class, then implemented interface,
- * before going up hierarchy.
- *
- * @return the message
- */
- private String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
- ValueStack valueStack) {
- if (checked == null) {
- checked = new TreeSet<>();
- } else if (checked.contains(clazz.getName())) {
- return null;
- }
-
- // look in properties of this class
- String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
-
- if (indexedKey != null) {
- msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
- }
-
- // look in properties of implemented interfaces
- Class[] interfaces = clazz.getInterfaces();
-
- for (Class anInterface : interfaces) {
- msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
-
- if (indexedKey != null) {
- msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
- }
- }
-
- // traverse up hierarchy
- if (clazz.isInterface()) {
- interfaces = clazz.getInterfaces();
-
- for (Class anInterface : interfaces) {
- msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
-
- if (msg != null) {
- return msg;
- }
- }
- } else {
- if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
- return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
- }
- }
-
- return null;
- }
-
- /**
* Clears all the internal lists.
*
* @deprecated used only in tests
[03/10] struts git commit: WW-4762 Uses Struts prefix for the main
implementation
Posted by lu...@apache.org.
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java
deleted file mode 100644
index a9dca08..0000000
--- a/core/src/test/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProviderTest.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright 2002-2006,2009 The Apache Software Foundation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.opensymphony.xwork2.util;
-
-import com.mockobjects.dynamic.Mock;
-import com.opensymphony.xwork2.*;
-import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
-import com.opensymphony.xwork2.test.ModelDrivenAction2;
-import com.opensymphony.xwork2.test.TestBean2;
-
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.ResourceBundle;
-
-
-/**
- * Unit test for {@link DefaultLocalizedTextProvider}.
- *
- * @author jcarreira
- * @author tm_jee
- *
- * @version $Date$ $Id$
- */
-public class DefaultLocalizedTextProviderTest extends XWorkTestCase {
-
- private LocalizedTextProvider localizedTextProvider;
-
- public void testNpeWhenClassIsPrimitive() throws Exception {
- ValueStack stack = ActionContext.getContext().getValueStack();
- stack.push(new MyObject());
- String result = localizedTextProvider.findText(MyObject.class, "someObj.someI18nKey", Locale.ENGLISH, "default message", null, stack);
- System.out.println(result);
- }
-
- public static class MyObject extends ActionSupport {
- public boolean getSomeObj() {
- return true;
- }
- }
-
- public void testActionGetTextWithNullObject() throws Exception {
- MyAction action = new MyAction();
- container.inject(action);
-
- Mock mockActionInvocation = new Mock(ActionInvocation.class);
- mockActionInvocation.expectAndReturn("getAction", action);
- ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
- ActionContext.getContext().getValueStack().push(action);
-
- String message = action.getText("barObj.title");
- assertEquals("Title:", message);
- }
-
-
- public static class MyAction extends ActionSupport {
- private Bar testBean2;
-
- public Bar getBarObj() {
- return testBean2;
- }
- public void setBarObj(Bar testBean2) {
- this.testBean2 = testBean2;
- }
- }
-
- public void testActionGetText() throws Exception {
- ModelDrivenAction2 action = new ModelDrivenAction2();
- container.inject(action);
-
- TestBean2 bean = (TestBean2) action.getModel();
- Bar bar = new Bar();
- bean.setBarObj(bar);
-
- Mock mockActionInvocation = new Mock(ActionInvocation.class);
- mockActionInvocation.expectAndReturn("getAction", action);
- ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
- ActionContext.getContext().getValueStack().push(action);
- ActionContext.getContext().getValueStack().push(action.getModel());
-
- String message = action.getText("barObj.title");
- assertEquals("Title:", message);
- }
-
- public void testNullKeys() {
- localizedTextProvider.findText(this.getClass(), null, Locale.getDefault());
- }
-
- public void testActionGetTextXXX() throws Exception {
- localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/FindMe");
-
- SimpleAction action = new SimpleAction();
- container.inject(action);
-
- Mock mockActionInvocation = new Mock(ActionInvocation.class);
- mockActionInvocation.expectAndReturn("getAction", action);
- ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
- ActionContext.getContext().getValueStack().push(action);
-
- String message = action.getText("bean.name");
- String foundBean2 = action.getText("bean2.name");
-
- assertEquals("Okay! You found Me!", foundBean2);
- assertEquals("Haha you cant FindMe!", message);
- }
-
- public void testAddDefaultResourceBundle() {
- String text = localizedTextProvider.findDefaultText("foo.range", Locale.getDefault());
- assertNull("Found message when it should not be available.", text);
-
- localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
-
- String message = localizedTextProvider.findDefaultText("foo.range", Locale.US);
- assertEquals("Foo Range Message", message);
- }
-
- public void testAddDefaultResourceBundle2() throws Exception {
- localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
-
- ActionProxy proxy = actionProxyFactory.createActionProxy("/", "packagelessAction", null, new HashMap<String, Object>(), false, true);
- proxy.execute();
- }
-
- public void testDefaultMessage() throws Exception {
- String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
- assertEquals("Error during Action invocation", message);
- }
-
- public void testDefaultMessageOverride() throws Exception {
- String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
- assertEquals("Error during Action invocation", message);
-
- localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/test");
-
- message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
- assertEquals("Testing resource bundle override", message);
- }
-
- public void testFindTextInChildProperty() throws Exception {
- ModelDriven action = new ModelDrivenAction2();
- TestBean2 bean = (TestBean2) action.getModel();
- Bar bar = new Bar();
- bean.setBarObj(bar);
-
- Mock mockActionInvocation = new Mock(ActionInvocation.class);
- mockActionInvocation.expectAndReturn("hashCode", 0);
- mockActionInvocation.expectAndReturn("getAction", action);
- ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
- ActionContext.getContext().getValueStack().push(action);
- ActionContext.getContext().getValueStack().push(action.getModel());
-
- String message = localizedTextProvider.findText(ModelDrivenAction2.class, "invalid.fieldvalue.barObj.title", Locale.getDefault());
- assertEquals("Title is invalid!", message);
- }
-
- public void testFindTextInInterface() throws Exception {
- Action action = new ModelDrivenAction2();
- Mock mockActionInvocation = new Mock(ActionInvocation.class);
- mockActionInvocation.expectAndReturn("getAction", action);
- ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-
- String message = localizedTextProvider.findText(ModelDrivenAction2.class, "test.foo", Locale.getDefault());
- assertEquals("Foo!", message);
- }
-
- public void testFindTextInPackage() throws Exception {
- ModelDriven action = new ModelDrivenAction2();
-
- Mock mockActionInvocation = new Mock(ActionInvocation.class);
- mockActionInvocation.expectAndReturn("getAction", action);
- ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
-
- String message = localizedTextProvider.findText(ModelDrivenAction2.class, "package.properties", Locale.getDefault());
- assertEquals("It works!", message);
- }
-
- public void testParameterizedDefaultMessage() throws Exception {
- String message = localizedTextProvider.findDefaultText("xwork.exception.missing-action", Locale.getDefault(), new String[]{"AddUser"});
- assertEquals("There is no Action mapped for action name AddUser.", message);
- }
-
- public void testParameterizedDefaultMessageWithPackage() throws Exception {
- String message = localizedTextProvider.findDefaultText("xwork.exception.missing-package-action", Locale.getDefault(), new String[]{"blah", "AddUser"});
- assertEquals("There is no Action mapped for namespace blah and action name AddUser.", message);
- }
-
- public void testLocalizedDateFormatIsUsed() throws ParseException {
- localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
- Date date = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse("01/01/2015");
- Object[] params = new Object[]{ date };
- String usDate = localizedTextProvider.findDefaultText("test.format.date", Locale.US, params);
- String germanDate = localizedTextProvider.findDefaultText("test.format.date", Locale.GERMANY, params);
- assertEquals(usDate, "1/1/15");
- assertEquals(germanDate, "01.01.15");
- }
-
- public void testXW377() {
- localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
-
- String text = localizedTextProvider.findText(Bar.class, "xw377", ActionContext.getContext().getLocale(), "xw377", null, ActionContext.getContext().getValueStack());
- assertEquals("xw377", text); // should not log
-
- String text2 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "notinbundle", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
- assertEquals("hello", text2); // should log WARN
-
- String text3 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "notinbundle.key", ActionContext.getContext().getLocale(), "notinbundle.key", null, ActionContext.getContext().getValueStack());
- assertEquals("notinbundle.key", text3); // should log WARN
-
- String text4 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "xw377", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
- assertEquals("xw377", text4); // should not log
-
- String text5 = localizedTextProvider.findText(DefaultLocalizedTextProviderTest.class, "username", ActionContext.getContext().getLocale(), null, null, ActionContext.getContext().getValueStack());
- assertEquals("Santa", text5); // should not log
- }
-
- public void testXW404() {
- // This tests will try to load bundles from the 3 locales but we only have files for France and Germany.
- // Before this fix loading the bundle for Germany failed since Italy have previously failed and thus the misses cache
- // contained a false entry
-
- ResourceBundle rbFrance = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.FRANCE);
- ResourceBundle rbItaly = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.ITALY);
- ResourceBundle rbGermany = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.GERMANY);
-
- assertNotNull(rbFrance);
- assertEquals("Bonjour", rbFrance.getString("hello"));
-
- assertNull(rbItaly);
-
- assertNotNull(rbGermany);
- assertEquals("Hallo", rbGermany.getString("hello"));
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- XmlConfigurationProvider provider = new XmlConfigurationProvider("xwork-sample.xml");
- container.inject(provider);
- loadConfigurationProviders(provider);
-
- localizedTextProvider = container.getInstance(LocalizedTextProvider.class);
-
- ActionContext.getContext().setLocale(Locale.US);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- localizedTextProvider = null;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
new file mode 100644
index 0000000..237db80
--- /dev/null
+++ b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.util;
+
+import com.mockobjects.dynamic.Mock;
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
+import com.opensymphony.xwork2.test.ModelDrivenAction2;
+import com.opensymphony.xwork2.test.TestBean2;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+
+/**
+ * Unit test for {@link StrutsLocalizedTextProvider}.
+ *
+ * @author jcarreira
+ * @author tm_jee
+ *
+ * @version $Date$ $Id$
+ */
+public class StrutsLocalizedTextProviderTest extends XWorkTestCase {
+
+ private LocalizedTextProvider localizedTextProvider;
+
+ public void testNpeWhenClassIsPrimitive() throws Exception {
+ ValueStack stack = ActionContext.getContext().getValueStack();
+ stack.push(new MyObject());
+ String result = localizedTextProvider.findText(MyObject.class, "someObj.someI18nKey", Locale.ENGLISH, "default message", null, stack);
+ System.out.println(result);
+ }
+
+ public static class MyObject extends ActionSupport {
+ public boolean getSomeObj() {
+ return true;
+ }
+ }
+
+ public void testActionGetTextWithNullObject() throws Exception {
+ MyAction action = new MyAction();
+ container.inject(action);
+
+ Mock mockActionInvocation = new Mock(ActionInvocation.class);
+ mockActionInvocation.expectAndReturn("getAction", action);
+ ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+ ActionContext.getContext().getValueStack().push(action);
+
+ String message = action.getText("barObj.title");
+ assertEquals("Title:", message);
+ }
+
+
+ public static class MyAction extends ActionSupport {
+ private Bar testBean2;
+
+ public Bar getBarObj() {
+ return testBean2;
+ }
+ public void setBarObj(Bar testBean2) {
+ this.testBean2 = testBean2;
+ }
+ }
+
+ public void testActionGetText() throws Exception {
+ ModelDrivenAction2 action = new ModelDrivenAction2();
+ container.inject(action);
+
+ TestBean2 bean = (TestBean2) action.getModel();
+ Bar bar = new Bar();
+ bean.setBarObj(bar);
+
+ Mock mockActionInvocation = new Mock(ActionInvocation.class);
+ mockActionInvocation.expectAndReturn("getAction", action);
+ ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+ ActionContext.getContext().getValueStack().push(action);
+ ActionContext.getContext().getValueStack().push(action.getModel());
+
+ String message = action.getText("barObj.title");
+ assertEquals("Title:", message);
+ }
+
+ public void testNullKeys() {
+ localizedTextProvider.findText(this.getClass(), null, Locale.getDefault());
+ }
+
+ public void testActionGetTextXXX() throws Exception {
+ localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/FindMe");
+
+ SimpleAction action = new SimpleAction();
+ container.inject(action);
+
+ Mock mockActionInvocation = new Mock(ActionInvocation.class);
+ mockActionInvocation.expectAndReturn("getAction", action);
+ ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+ ActionContext.getContext().getValueStack().push(action);
+
+ String message = action.getText("bean.name");
+ String foundBean2 = action.getText("bean2.name");
+
+ assertEquals("Okay! You found Me!", foundBean2);
+ assertEquals("Haha you cant FindMe!", message);
+ }
+
+ public void testAddDefaultResourceBundle() {
+ String text = localizedTextProvider.findDefaultText("foo.range", Locale.getDefault());
+ assertNull("Found message when it should not be available.", text);
+
+ localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
+
+ String message = localizedTextProvider.findDefaultText("foo.range", Locale.US);
+ assertEquals("Foo Range Message", message);
+ }
+
+ public void testAddDefaultResourceBundle2() throws Exception {
+ localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/SimpleAction");
+
+ ActionProxy proxy = actionProxyFactory.createActionProxy("/", "packagelessAction", null, new HashMap<String, Object>(), false, true);
+ proxy.execute();
+ }
+
+ public void testDefaultMessage() throws Exception {
+ String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
+ assertEquals("Error during Action invocation", message);
+ }
+
+ public void testDefaultMessageOverride() throws Exception {
+ String message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
+ assertEquals("Error during Action invocation", message);
+
+ localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/test");
+
+ message = localizedTextProvider.findDefaultText("xwork.error.action.execution", Locale.getDefault());
+ assertEquals("Testing resource bundle override", message);
+ }
+
+ public void testFindTextInChildProperty() throws Exception {
+ ModelDriven action = new ModelDrivenAction2();
+ TestBean2 bean = (TestBean2) action.getModel();
+ Bar bar = new Bar();
+ bean.setBarObj(bar);
+
+ Mock mockActionInvocation = new Mock(ActionInvocation.class);
+ mockActionInvocation.expectAndReturn("hashCode", 0);
+ mockActionInvocation.expectAndReturn("getAction", action);
+ ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+ ActionContext.getContext().getValueStack().push(action);
+ ActionContext.getContext().getValueStack().push(action.getModel());
+
+ String message = localizedTextProvider.findText(ModelDrivenAction2.class, "invalid.fieldvalue.barObj.title", Locale.getDefault());
+ assertEquals("Title is invalid!", message);
+ }
+
+ public void testFindTextInInterface() throws Exception {
+ Action action = new ModelDrivenAction2();
+ Mock mockActionInvocation = new Mock(ActionInvocation.class);
+ mockActionInvocation.expectAndReturn("getAction", action);
+ ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+
+ String message = localizedTextProvider.findText(ModelDrivenAction2.class, "test.foo", Locale.getDefault());
+ assertEquals("Foo!", message);
+ }
+
+ public void testFindTextInPackage() throws Exception {
+ ModelDriven action = new ModelDrivenAction2();
+
+ Mock mockActionInvocation = new Mock(ActionInvocation.class);
+ mockActionInvocation.expectAndReturn("getAction", action);
+ ActionContext.getContext().setActionInvocation((ActionInvocation) mockActionInvocation.proxy());
+
+ String message = localizedTextProvider.findText(ModelDrivenAction2.class, "package.properties", Locale.getDefault());
+ assertEquals("It works!", message);
+ }
+
+ public void testParameterizedDefaultMessage() throws Exception {
+ String message = localizedTextProvider.findDefaultText("xwork.exception.missing-action", Locale.getDefault(), new String[]{"AddUser"});
+ assertEquals("There is no Action mapped for action name AddUser.", message);
+ }
+
+ public void testParameterizedDefaultMessageWithPackage() throws Exception {
+ String message = localizedTextProvider.findDefaultText("xwork.exception.missing-package-action", Locale.getDefault(), new String[]{"blah", "AddUser"});
+ assertEquals("There is no Action mapped for namespace blah and action name AddUser.", message);
+ }
+
+ public void testLocalizedDateFormatIsUsed() throws ParseException {
+ localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
+ Date date = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse("01/01/2015");
+ Object[] params = new Object[]{ date };
+ String usDate = localizedTextProvider.findDefaultText("test.format.date", Locale.US, params);
+ String germanDate = localizedTextProvider.findDefaultText("test.format.date", Locale.GERMANY, params);
+ assertEquals(usDate, "1/1/15");
+ assertEquals(germanDate, "01.01.15");
+ }
+
+ public void testXW377() {
+ localizedTextProvider.addDefaultResourceBundle("com/opensymphony/xwork2/util/LocalizedTextUtilTest");
+
+ String text = localizedTextProvider.findText(Bar.class, "xw377", ActionContext.getContext().getLocale(), "xw377", null, ActionContext.getContext().getValueStack());
+ assertEquals("xw377", text); // should not log
+
+ String text2 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "notinbundle", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
+ assertEquals("hello", text2); // should log WARN
+
+ String text3 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "notinbundle.key", ActionContext.getContext().getLocale(), "notinbundle.key", null, ActionContext.getContext().getValueStack());
+ assertEquals("notinbundle.key", text3); // should log WARN
+
+ String text4 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "xw377", ActionContext.getContext().getLocale(), "hello", null, ActionContext.getContext().getValueStack());
+ assertEquals("xw377", text4); // should not log
+
+ String text5 = localizedTextProvider.findText(StrutsLocalizedTextProviderTest.class, "username", ActionContext.getContext().getLocale(), null, null, ActionContext.getContext().getValueStack());
+ assertEquals("Santa", text5); // should not log
+ }
+
+ public void testXW404() {
+ // This tests will try to load bundles from the 3 locales but we only have files for France and Germany.
+ // Before this fix loading the bundle for Germany failed since Italy have previously failed and thus the misses cache
+ // contained a false entry
+
+ ResourceBundle rbFrance = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.FRANCE);
+ ResourceBundle rbItaly = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.ITALY);
+ ResourceBundle rbGermany = localizedTextProvider.findResourceBundle("com/opensymphony/xwork2/util/XW404", Locale.GERMANY);
+
+ assertNotNull(rbFrance);
+ assertEquals("Bonjour", rbFrance.getString("hello"));
+
+ assertNull(rbItaly);
+
+ assertNotNull(rbGermany);
+ assertEquals("Hallo", rbGermany.getString("hello"));
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ XmlConfigurationProvider provider = new XmlConfigurationProvider("xwork-sample.xml");
+ container.inject(provider);
+ loadConfigurationProviders(provider);
+
+ localizedTextProvider = container.getInstance(LocalizedTextProvider.class);
+
+ ActionContext.getContext().setLocale(Locale.US);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ localizedTextProvider = null;
+ }
+
+}
[09/10] struts git commit: WW-4762 Defines default-only bean
Posted by lu...@apache.org.
WW-4762 Defines default-only bean
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/46043336
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/46043336
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/46043336
Branch: refs/heads/master
Commit: 4604333649a0060421c07cd5520c8285ca0c135f
Parents: fd168c4
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed May 10 15:48:35 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed May 10 15:48:35 2017 +0200
----------------------------------------------------------------------
core/src/main/resources/struts-default.xml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/46043336/core/src/main/resources/struts-default.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index ead2954..bf2c1f7 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -130,8 +130,10 @@
<bean type="com.opensymphony.xwork2.conversion.impl.NumberConverter" name="struts" class="com.opensymphony.xwork2.conversion.impl.NumberConverter" scope="singleton"/>
<bean type="com.opensymphony.xwork2.conversion.impl.StringConverter" name="struts" class="com.opensymphony.xwork2.conversion.impl.StringConverter" scope="singleton"/>
- <bean type="com.opensymphony.xwork2.TextProvider" name="system" class="com.opensymphony.xwork2.DefaultTextProvider" scope="singleton" />
+ <bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="only-default" class="com.opensymphony.xwork2.util.DefaultLocalizedTextProvider" scope="singleton" />
<bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="struts" class="com.opensymphony.xwork2.util.StrutsLocalizedTextProvider" scope="singleton" />
+
+ <bean type="com.opensymphony.xwork2.TextProvider" name="system" class="com.opensymphony.xwork2.DefaultTextProvider" scope="singleton" />
<bean type="com.opensymphony.xwork2.TextProviderFactory" name="struts" class="com.opensymphony.xwork2.StrutsTextProviderFactory" scope="singleton" />
<bean type="com.opensymphony.xwork2.LocaleProviderFactory" name="struts" class="com.opensymphony.xwork2.DefaultLocaleProviderFactory" scope="singleton" />
[04/10] struts git commit: WW-4762 Uses Struts prefix for the main
implementation
Posted by lu...@apache.org.
WW-4762 Uses Struts prefix for the main implementation
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/8bf77a1d
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/8bf77a1d
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/8bf77a1d
Branch: refs/heads/master
Commit: 8bf77a1d19d31dbc8574dcbf958c3da2a4724c84
Parents: 3ffc9b8
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 11:56:48 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 11:56:48 2017 +0200
----------------------------------------------------------------------
.../config/impl/DefaultConfiguration.java | 2 +-
.../providers/XWorkConfigurationProvider.java | 4 +-
.../util/DefaultLocalizedTextProvider.java | 1000 ------------------
.../util/StrutsLocalizedTextProvider.java | 1000 ++++++++++++++++++
core/src/main/resources/struts-default.xml | 2 +-
.../util/DefaultLocalizedTextProviderTest.java | 267 -----
.../util/StrutsLocalizedTextProviderTest.java | 267 +++++
7 files changed, 1271 insertions(+), 1271 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
index 8882a2f..a1b6490 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
@@ -261,7 +261,7 @@ public class DefaultConfiguration implements Configuration {
builder.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON);
- builder.factory(LocalizedTextProvider.class, DefaultLocalizedTextProvider.class, Scope.SINGLETON);
+ builder.factory(LocalizedTextProvider.class, StrutsLocalizedTextProvider.class, Scope.SINGLETON);
builder.factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON);
builder.factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON);
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
index 1bc6986..e5692a1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
@@ -72,7 +72,7 @@ import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.LocalizedTextProvider;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
+import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.TextParser;
@@ -182,7 +182,7 @@ public class XWorkConfigurationProvider implements ConfigurationProvider {
.factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON)
.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON)
- .factory(LocalizedTextProvider.class, DefaultLocalizedTextProvider.class, Scope.SINGLETON)
+ .factory(LocalizedTextProvider.class, StrutsLocalizedTextProvider.class, Scope.SINGLETON)
.factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON)
.factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON)
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
deleted file mode 100644
index 21f7477..0000000
--- a/core/src/main/java/com/opensymphony/xwork2/util/DefaultLocalizedTextProvider.java
+++ /dev/null
@@ -1,1000 +0,0 @@
-/*
- * $Id$
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package com.opensymphony.xwork2.util;
-
-import com.opensymphony.xwork2.ActionContext;
-import com.opensymphony.xwork2.ActionInvocation;
-import com.opensymphony.xwork2.LocalizedTextProvider;
-import com.opensymphony.xwork2.ModelDriven;
-import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
-import com.opensymphony.xwork2.inject.Inject;
-import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory;
-import org.apache.commons.lang3.ObjectUtils;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.apache.struts2.StrutsConstants;
-
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.text.MessageFormat;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * Provides support for localization in the framework, it can be used to read only default bundles,
- * or it can search the class hierarchy to find proper bundles.
- */
-public class DefaultLocalizedTextProvider implements LocalizedTextProvider {
-
- private static final Logger LOG = LogManager.getLogger(DefaultLocalizedTextProvider.class);
-
- public static final String XWORK_MESSAGES_BUNDLE = "com/opensymphony/xwork2/xwork-messages";
- public static final String STRUTS_MESSAGES_BUNDLE = "org/apache/struts2/struts-messages";
-
- private static final String TOMCAT_RESOURCE_ENTRIES_FIELD = "resourceEntries";
-
- private final ConcurrentMap<Integer, List<String>> classLoaderMap = new ConcurrentHashMap<>();
-
- private boolean reloadBundles = false;
- private boolean devMode = false;
-
- private final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<>();
- private final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<>();
- private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>();
- private final Set<String> missingBundles = Collections.synchronizedSet(new HashSet<String>());
-
- private final String RELOADED = "com.opensymphony.xwork2.util.LocalizedTextUtil.reloaded";
-
- /**
- * Clears the internal list of resource bundles.
- *
- * @deprecated used only in tests
- */
- @Deprecated
- public static void clearDefaultResourceBundles() {
- // no-op
- }
-
- public DefaultLocalizedTextProvider() {
-
- addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE);
- addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
- }
-
- /**
- * Should resorce bundles be reloaded.
- *
- * @param reloadBundles reload bundles?
- */
- @Inject(value = StrutsConstants.STRUTS_I18N_RELOAD, required = false)
- public void setReloadBundles(String reloadBundles) {
- this.reloadBundles = Boolean.parseBoolean(reloadBundles);
- }
-
- @Inject(value = StrutsConstants.STRUTS_DEVMODE, required = false)
- public void setDevMode(String devMode) {
- this.devMode = Boolean.parseBoolean(devMode);
- }
-
- @Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false)
- public void setCustomI18NResources(String bundles) {
- if (bundles != null && bundles.length() > 0) {
- StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
-
- while (customBundles.hasMoreTokens()) {
- String name = customBundles.nextToken();
- try {
- LOG.trace("Loading global messages from [{}]", name);
- addDefaultResourceBundle(name);
- } catch (Exception e) {
- LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
- }
- }
- }
- }
-
- /**
- * Add's the bundle to the internal list of default bundles.
- * <p>
- * If the bundle already exists in the list it will be readded.
- * </p>
- *
- * @param resourceBundleName the name of the bundle to add.
- */
- @Override
- public void addDefaultResourceBundle(String resourceBundleName) {
- //make sure this doesn't get added more than once
- final ClassLoader ccl = getCurrentThreadContextClassLoader();
- synchronized (XWORK_MESSAGES_BUNDLE) {
- List<String> bundles = classLoaderMap.get(ccl.hashCode());
- if (bundles == null) {
- bundles = new CopyOnWriteArrayList<>();
- classLoaderMap.put(ccl.hashCode(), bundles);
- }
- bundles.remove(resourceBundleName);
- bundles.add(0, resourceBundleName);
- }
-
- if (LOG.isDebugEnabled()) {
- LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", resourceBundleName, ccl.toString());
- }
- }
-
- /**
- * Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale
- * with language "en", country "US" and variant "foo". This will parse the output of
- * {@link java.util.Locale#toString()}.
- *
- * @param localeStr The locale String to parse.
- * @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
- * @return requested Locale
- *
- * @deprecated please use {@link org.apache.commons.lang3.LocaleUtils#toLocale(String)}
- */
- @Deprecated
- public static Locale localeFromString(String localeStr, Locale defaultLocale) {
- if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) {
- if (defaultLocale != null) {
- return defaultLocale;
- }
- return Locale.getDefault();
- }
-
- int index = localeStr.indexOf('_');
- if (index < 0) {
- return new Locale(localeStr);
- }
-
- String language = localeStr.substring(0, index);
- if (index == localeStr.length()) {
- return new Locale(language);
- }
-
- localeStr = localeStr.substring(index + 1);
- index = localeStr.indexOf('_');
- if (index < 0) {
- return new Locale(language, localeStr);
- }
-
- String country = localeStr.substring(0, index);
- if (index == localeStr.length()) {
- return new Locale(language, country);
- }
-
- localeStr = localeStr.substring(index + 1);
- return new Locale(language, country, localeStr);
- }
-
- /**
- * Returns a localized message for the specified key, aTextName. Neither the key nor the
- * message is evaluated.
- *
- * @param aTextName the message key
- * @param locale the locale the message should be for
- * @return a localized message based on the specified key, or null if no localized message can be found for it
- */
- @Override
- public String findDefaultText(String aTextName, Locale locale) {
- List<String> localList = classLoaderMap.get(Thread.currentThread().getContextClassLoader().hashCode());
-
- for (String bundleName : localList) {
- ResourceBundle bundle = findResourceBundle(bundleName, locale);
- if (bundle != null) {
- reloadBundles();
- try {
- return bundle.getString(aTextName);
- } catch (MissingResourceException e) {
- // will be logged when not found in any bundle
- }
- }
- }
-
- if (devMode) {
- LOG.warn("Missing key [{}] in bundles [{}]!", aTextName, localList);
- } else {
- LOG.debug("Missing key [{}] in bundles [{}]!", aTextName, localList);
- }
-
- return null;
- }
-
- /**
- * Returns a localized message for the specified key, aTextName, substituting variables from the
- * array of params into the message. Neither the key nor the message is evaluated.
- *
- * @param aTextName the message key
- * @param locale the locale the message should be for
- * @param params an array of objects to be substituted into the message text
- * @return A formatted message based on the specified key, or null if no localized message can be found for it
- */
- @Override
- public String findDefaultText(String aTextName, Locale locale, Object[] params) {
- String defaultText = findDefaultText(aTextName, locale);
- if (defaultText != null) {
- MessageFormat mf = buildMessageFormat(defaultText, locale);
- return formatWithNullDetection(mf, params);
- }
- return null;
- }
-
- /**
- * Finds the given resource bundle by it's name.
- * <p>
- * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
- * </p>
- *
- * @param aBundleName the name of the bundle (usually it's FQN classname).
- * @param locale the locale.
- * @return the bundle, <tt>null</tt> if not found.
- */
- @Override
- public ResourceBundle findResourceBundle(String aBundleName, Locale locale) {
- ClassLoader classLoader = getCurrentThreadContextClassLoader();
- String key = createMissesKey(String.valueOf(classLoader.hashCode()), aBundleName, locale);
-
- if (missingBundles.contains(key)) {
- return null;
- }
-
- ResourceBundle bundle = null;
- try {
- if (bundlesMap.containsKey(key)) {
- bundle = bundlesMap.get(key);
- } else {
- bundle = ResourceBundle.getBundle(aBundleName, locale, classLoader);
- bundlesMap.putIfAbsent(key, bundle);
- }
- } catch (MissingResourceException ex) {
- if (delegatedClassLoaderMap.containsKey(classLoader.hashCode())) {
- try {
- if (bundlesMap.containsKey(key)) {
- bundle = bundlesMap.get(key);
- } else {
- bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode()));
- bundlesMap.putIfAbsent(key, bundle);
- }
- } catch (MissingResourceException e) {
- LOG.debug("Missing resource bundle [{}]!", aBundleName, e);
- missingBundles.add(key);
- }
- } else {
- LOG.debug("Missing resource bundle [{}]!", aBundleName);
- missingBundles.add(key);
- }
- }
- return bundle;
- }
-
- /**
- * @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader
- */
- public void setDelegatedClassLoader(final ClassLoader classLoader) {
- synchronized (bundlesMap) {
- delegatedClassLoaderMap.put(getCurrentThreadContextClassLoader().hashCode(), classLoader);
- }
- }
-
- /**
- * @param bundleName Removes the bundle from any cached "misses"
- */
- public void clearBundle(final String bundleName) {
- bundlesMap.remove(getCurrentThreadContextClassLoader().hashCode() + bundleName);
- }
-
-
- /**
- * Creates a key to used for lookup/storing in the bundle misses cache.
- *
- * @param prefix the prefix for the returning String - it is supposed to be the ClassLoader hash code.
- * @param aBundleName the name of the bundle (usually it's FQN classname).
- * @param locale the locale.
- * @return the key to use for lookup/storing in the bundle misses cache.
- */
- private String createMissesKey(String prefix, String aBundleName, Locale locale) {
- return prefix + aBundleName + "_" + locale.toString();
- }
-
- /**
- * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
- * with aTextName as the default message.
- *
- * @param aClass class name
- * @param aTextName text name
- * @param locale the locale
- *
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
- */
- @Override
- public String findText(Class aClass, String aTextName, Locale locale) {
- return findText(aClass, aTextName, locale, aTextName, new Object[0]);
- }
-
- /**
- * <p>
- * Finds a localized text message for the given key, aTextName. Both the key and the message
- * itself is evaluated as required. The following algorithm is used to find the requested
- * message:
- * </p>
- *
- * <ol>
- * <li>Look for message in aClass' class hierarchy.
- * <ol>
- * <li>Look for the message in a resource bundle for aClass</li>
- * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
- * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
- * </ol></li>
- * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
- * the model's class hierarchy (repeat sub-steps listed above).</li>
- * <li>If not found, look for message in child property. This is determined by evaluating
- * the message key as an OGNL expression. For example, if the key is
- * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
- * object. If so, repeat the entire process fromthe beginning with the object's class as
- * aClass and "address.state" as the message key.</li>
- * <li>If not found, look for the message in aClass' package hierarchy.</li>
- * <li>If still not found, look for the message in the default resource bundles.</li>
- * <li>Return defaultMessage</li>
- * </ol>
- *
- * <p>
- * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
- * message for that specific key cannot be found, the general form will also be looked up
- * (i.e. user.phone[*]).
- * </p>
- *
- * <p>
- * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
- * will be treated as an OGNL expression and evaluated as such.
- * </p>
- *
- * @param aClass the class whose name to use as the start point for the search
- * @param aTextName the key to find the text message for
- * @param locale the locale the message should be for
- * @param defaultMessage the message to be returned if no text message can be found in any
- * resource bundle
- * @param args arguments
- * resource bundle
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- */
- @Override
- public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
- ValueStack valueStack = ActionContext.getContext().getValueStack();
- return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
-
- }
-
- /**
- * <p>
- * Finds a localized text message for the given key, aTextName. Both the key and the message
- * itself is evaluated as required. The following algorithm is used to find the requested
- * message:
- * </p>
- *
- * <ol>
- * <li>Look for message in aClass' class hierarchy.
- * <ol>
- * <li>Look for the message in a resource bundle for aClass</li>
- * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
- * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
- * </ol></li>
- * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
- * the model's class hierarchy (repeat sub-steps listed above).</li>
- * <li>If not found, look for message in child property. This is determined by evaluating
- * the message key as an OGNL expression. For example, if the key is
- * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
- * object. If so, repeat the entire process fromthe beginning with the object's class as
- * aClass and "address.state" as the message key.</li>
- * <li>If not found, look for the message in aClass' package hierarchy.</li>
- * <li>If still not found, look for the message in the default resource bundles.</li>
- * <li>Return defaultMessage</li>
- * </ol>
- *
- * <p>
- * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
- * message for that specific key cannot be found, the general form will also be looked up
- * (i.e. user.phone[*]).
- * </p>
- *
- * <p>
- * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
- * will be treated as an OGNL expression and evaluated as such.
- * </p>
- *
- * <p>
- * If a message is <b>not</b> found a WARN log will be logged.
- * </p>
- *
- * @param aClass the class whose name to use as the start point for the search
- * @param aTextName the key to find the text message for
- * @param locale the locale the message should be for
- * @param defaultMessage the message to be returned if no text message can be found in any
- * resource bundle
- * @param args arguments
- * @param valueStack the value stack to use to evaluate expressions instead of the
- * one in the ActionContext ThreadLocal
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- */
- @Override
- public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args,
- ValueStack valueStack) {
- String indexedTextName = null;
- if (aTextName == null) {
- LOG.warn("Trying to find text with null key!");
- aTextName = "";
- }
- // calculate indexedTextName (collection[*]) if applicable
- if (aTextName.contains("[")) {
- int i = -1;
-
- indexedTextName = aTextName;
-
- while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
- int j = indexedTextName.indexOf("]", i);
- String a = indexedTextName.substring(0, i);
- String b = indexedTextName.substring(j);
- indexedTextName = a + "[*" + b;
- }
- }
-
- // search up class hierarchy
- String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
-
- if (msg != null) {
- return msg;
- }
-
- if (ModelDriven.class.isAssignableFrom(aClass)) {
- ActionContext context = ActionContext.getContext();
- // search up model's class hierarchy
- ActionInvocation actionInvocation = context.getActionInvocation();
-
- // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null
- if (actionInvocation != null) {
- Object action = actionInvocation.getAction();
- if (action instanceof ModelDriven) {
- Object model = ((ModelDriven) action).getModel();
- if (model != null) {
- msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack);
- if (msg != null) {
- return msg;
- }
- }
- }
- }
- }
-
- // nothing still? alright, search the package hierarchy now
- for (Class clazz = aClass;
- (clazz != null) && !clazz.equals(Object.class);
- clazz = clazz.getSuperclass()) {
-
- String basePackageName = clazz.getName();
- while (basePackageName.lastIndexOf('.') != -1) {
- basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.'));
- String packageName = basePackageName + ".package";
- msg = getMessage(packageName, locale, aTextName, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
-
- if (indexedTextName != null) {
- msg = getMessage(packageName, locale, indexedTextName, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
- }
- }
- }
-
- // see if it's a child property
- int idx = aTextName.indexOf(".");
-
- if (idx != -1) {
- String newKey = null;
- String prop = null;
-
- if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
- idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
-
- if (idx != -1) {
- prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
- newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1);
- }
- } else {
- prop = aTextName.substring(0, idx);
- newKey = aTextName.substring(idx + 1);
- }
-
- if (prop != null) {
- Object obj = valueStack.findValue(prop);
- try {
- Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot());
- if (actionObj != null) {
- PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop);
-
- if (propertyDescriptor != null) {
- Class clazz = propertyDescriptor.getPropertyType();
-
- if (clazz != null) {
- if (obj != null) {
- valueStack.push(obj);
- }
- msg = findText(clazz, newKey, locale, null, args);
- if (obj != null) {
- valueStack.pop();
- }
- if (msg != null) {
- return msg;
- }
- }
- }
- }
- } catch (Exception e) {
- LOG.debug("unable to find property {}", prop, e);
- }
- }
- }
-
- // get default
- GetDefaultMessageReturnArg result;
- if (indexedTextName == null) {
- result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
- } else {
- result = getDefaultMessage(aTextName, locale, valueStack, args, null);
- if (result != null && result.message != null) {
- return result.message;
- }
- result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
- }
-
- // could we find the text, if not log a warn
- if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
- String warn = "Unable to find text for key '" + aTextName + "' ";
- if (indexedTextName != null) {
- warn += " or indexed key '" + indexedTextName + "' ";
- }
- warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
- LOG.debug(warn);
- }
-
- return result != null ? result.message : null;
- }
-
- /**
- * Determines if we found the text in the bundles.
- *
- * @param result the result so far
- * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
- */
- private boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
- if (result == null || result.message == null) {
- return true;
- }
-
- // did we find it in the bundle, then no problem?
- if (result.foundInBundle) {
- return false;
- }
-
- // not found in bundle
- return true;
- }
-
- /**
- * <p>
- * Finds a localized text message for the given key, aTextName, in the specified resource bundle
- * with aTextName as the default message.
- * </p>
- *
- * <p>
- * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
- * will be treated as an OGNL expression and evaluated as such.
- * </p>
- *
- * @param bundle a resource bundle name
- * @param aTextName text name
- * @param locale the locale
- *
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
- */
- @Override
- public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
- return findText(bundle, aTextName, locale, aTextName, new Object[0]);
- }
-
- /**
- * <p>
- * Finds a localized text message for the given key, aTextName, in the specified resource
- * bundle.
- * </p>
- *
- * <p>
- * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
- * will be treated as an OGNL expression and evaluated as such.
- * </p>
- *
- * <p>
- * If a message is <b>not</b> found a WARN log will be logged.
- * </p>
- *
- * @param bundle the bundle
- * @param aTextName the key
- * @param locale the locale
- * @param defaultMessage the default message to use if no message was found in the bundle
- * @param args arguments for the message formatter.
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- */
- @Override
- public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
- ValueStack valueStack = ActionContext.getContext().getValueStack();
- return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
- }
-
- /**
- * <p>
- * Finds a localized text message for the given key, aTextName, in the specified resource
- * bundle.
- * </p>
- *
- * <p>
- * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
- * will be treated as an OGNL expression and evaluated as such.
- * </p>
- *
- * <p>
- * If a message is <b>not</b> found a WARN log will be logged.
- * </p>
- *
- * @param bundle the bundle
- * @param aTextName the key
- * @param locale the locale
- * @param defaultMessage the default message to use if no message was found in the bundle
- * @param args arguments for the message formatter.
- * @param valueStack the OGNL value stack.
- * @return the localized text, or null if none can be found and no defaultMessage is provided
- */
- @Override
- public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
- ValueStack valueStack) {
- try {
- reloadBundles(valueStack.getContext());
-
- String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
- MessageFormat mf = buildMessageFormat(message, locale);
-
- return formatWithNullDetection(mf, args);
- } catch (MissingResourceException ex) {
- if (devMode) {
- LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
- } else {
- LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
- }
- }
-
- GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
- if (unableToFindTextForKey(result)) {
- LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
- }
- return result != null ? result.message : null;
- }
-
- /**
- * @return the default message.
- */
- private GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
- String defaultMessage) {
- GetDefaultMessageReturnArg result = null;
- boolean found = true;
-
- if (key != null) {
- String message = findDefaultText(key, locale);
-
- if (message == null) {
- message = defaultMessage;
- found = false; // not found in bundles
- }
-
- // defaultMessage may be null
- if (message != null) {
- MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
-
- String msg = formatWithNullDetection(mf, args);
- result = new GetDefaultMessageReturnArg(msg, found);
- }
- }
-
- return result;
- }
-
- /**
- * @return the message from the named resource bundle.
- */
- private String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
- ResourceBundle bundle = findResourceBundle(bundleName, locale);
- if (bundle == null) {
- return null;
- }
- if (valueStack != null)
- reloadBundles(valueStack.getContext());
- try {
- String message = bundle.getString(key);
- if (valueStack != null)
- message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
- MessageFormat mf = buildMessageFormat(message, locale);
- return formatWithNullDetection(mf, args);
- } catch (MissingResourceException e) {
- if (devMode) {
- LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
- } else {
- LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
- }
- return null;
- }
- }
-
- private String formatWithNullDetection(MessageFormat mf, Object[] args) {
- String message = mf.format(args);
- if ("null".equals(message)) {
- return null;
- } else {
- return message;
- }
- }
-
- private MessageFormat buildMessageFormat(String pattern, Locale locale) {
- MessageFormatKey key = new MessageFormatKey(pattern, locale);
- MessageFormat format = messageFormats.get(key);
- if (format == null) {
- format = new MessageFormat(pattern);
- format.setLocale(locale);
- format.applyPattern(pattern);
- messageFormats.put(key, format);
- }
-
- return format;
- }
-
- /**
- * Traverse up class hierarchy looking for message. Looks at class, then implemented interface,
- * before going up hierarchy.
- *
- * @return the message
- */
- private String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
- ValueStack valueStack) {
- if (checked == null) {
- checked = new TreeSet<String>();
- } else if (checked.contains(clazz.getName())) {
- return null;
- }
-
- // look in properties of this class
- String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
-
- if (indexedKey != null) {
- msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
- }
-
- // look in properties of implemented interfaces
- Class[] interfaces = clazz.getInterfaces();
-
- for (Class anInterface : interfaces) {
- msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
-
- if (indexedKey != null) {
- msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
-
- if (msg != null) {
- return msg;
- }
- }
- }
-
- // traverse up hierarchy
- if (clazz.isInterface()) {
- interfaces = clazz.getInterfaces();
-
- for (Class anInterface : interfaces) {
- msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
-
- if (msg != null) {
- return msg;
- }
- }
- } else {
- if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
- return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
- }
- }
-
- return null;
- }
-
- private void reloadBundles() {
- reloadBundles(ActionContext.getContext() != null ? ActionContext.getContext().getContextMap() : null);
- }
-
- private void reloadBundles(Map<String, Object> context) {
- if (reloadBundles) {
- try {
- Boolean reloaded;
- if (context != null) {
- reloaded = (Boolean) ObjectUtils.defaultIfNull(context.get(RELOADED), Boolean.FALSE);
- }else {
- reloaded = Boolean.FALSE;
- }
- if (!reloaded) {
- bundlesMap.clear();
- try {
- clearMap(ResourceBundle.class, null, "cacheList");
- } catch (NoSuchFieldException e) {
- // happens in IBM JVM, that has a different ResourceBundle impl
- // it has a 'cache' member
- clearMap(ResourceBundle.class, null, "cache");
- }
-
- // now, for the true and utter hack, if we're running in tomcat, clear
- // it's class loader resource cache as well.
- clearTomcatCache();
- if(context!=null) {
- context.put(RELOADED, true);
- }
- LOG.debug("Resource bundles reloaded");
- }
- } catch (Exception e) {
- LOG.error("Could not reload resource bundles", e);
- }
- }
- }
-
-
- private void clearTomcatCache() {
- ClassLoader loader = getCurrentThreadContextClassLoader();
- // no need for compilation here.
- Class cl = loader.getClass();
-
- try {
- if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) {
- clearMap(cl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
- } else {
- LOG.debug("Class loader {} is not tomcat loader.", cl.getName());
- }
- } catch (NoSuchFieldException nsfe) {
- if ("org.apache.catalina.loader.WebappClassLoaderBase".equals(cl.getSuperclass().getName())) {
- LOG.debug("Base class {} doesn't contain '{}' field, trying with parent!", cl.getName(), TOMCAT_RESOURCE_ENTRIES_FIELD, nsfe);
- try {
- clearMap(cl.getSuperclass(), loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
- } catch (Exception e) {
- LOG.warn("Couldn't clear tomcat cache using {}", cl.getSuperclass().getName(), e);
- }
- }
- } catch (Exception e) {
- LOG.warn("Couldn't clear tomcat cache", cl.getName(), e);
- }
- }
-
-
- private void clearMap(Class cl, Object obj, String name)
- throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
-
- Field field = cl.getDeclaredField(name);
- field.setAccessible(true);
-
- Object cache = field.get(obj);
-
- synchronized (cache) {
- Class ccl = cache.getClass();
- Method clearMethod = ccl.getMethod("clear");
- clearMethod.invoke(cache);
- }
- }
-
- /**
- * Clears all the internal lists.
- *
- * @deprecated used only in tests
- */
- @Deprecated
- public void reset() {
- // no-op
- }
-
- static class MessageFormatKey {
- String pattern;
- Locale locale;
-
- MessageFormatKey(String pattern, Locale locale) {
- this.pattern = pattern;
- this.locale = locale;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof MessageFormatKey)) return false;
-
- final MessageFormatKey messageFormatKey = (MessageFormatKey) o;
-
- if (locale != null ? !locale.equals(messageFormatKey.locale) : messageFormatKey.locale != null)
- return false;
- if (pattern != null ? !pattern.equals(messageFormatKey.pattern) : messageFormatKey.pattern != null)
- return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result;
- result = (pattern != null ? pattern.hashCode() : 0);
- result = 29 * result + (locale != null ? locale.hashCode() : 0);
- return result;
- }
- }
-
- private static ClassLoader getCurrentThreadContextClassLoader() {
- return Thread.currentThread().getContextClassLoader();
- }
-
- static class GetDefaultMessageReturnArg {
- String message;
- boolean foundInBundle;
-
- public GetDefaultMessageReturnArg(String message, boolean foundInBundle) {
- this.message = message;
- this.foundInBundle = foundInBundle;
- }
- }
-
- private static class EmptyResourceBundle extends ResourceBundle {
- @Override
- public Enumeration<String> getKeys() {
- return null; // dummy
- }
-
- @Override
- protected Object handleGetObject(String key) {
- return null; // dummy
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
new file mode 100644
index 0000000..78bf08d
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -0,0 +1,1000 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.LocalizedTextProvider;
+import com.opensymphony.xwork2.ModelDriven;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.message.ParameterizedMessage;
+import org.apache.struts2.StrutsConstants;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides support for localization in the framework, it can be used to read only default bundles,
+ * or it can search the class hierarchy to find proper bundles.
+ */
+public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
+
+ private static final Logger LOG = LogManager.getLogger(StrutsLocalizedTextProvider.class);
+
+ public static final String XWORK_MESSAGES_BUNDLE = "com/opensymphony/xwork2/xwork-messages";
+ public static final String STRUTS_MESSAGES_BUNDLE = "org/apache/struts2/struts-messages";
+
+ private static final String TOMCAT_RESOURCE_ENTRIES_FIELD = "resourceEntries";
+
+ private final ConcurrentMap<Integer, List<String>> classLoaderMap = new ConcurrentHashMap<>();
+
+ private boolean reloadBundles = false;
+ private boolean devMode = false;
+
+ private final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<>();
+ private final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>();
+ private final Set<String> missingBundles = Collections.synchronizedSet(new HashSet<String>());
+
+ private final String RELOADED = "com.opensymphony.xwork2.util.LocalizedTextUtil.reloaded";
+
+ /**
+ * Clears the internal list of resource bundles.
+ *
+ * @deprecated used only in tests
+ */
+ @Deprecated
+ public static void clearDefaultResourceBundles() {
+ // no-op
+ }
+
+ public StrutsLocalizedTextProvider() {
+
+ addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE);
+ addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
+ }
+
+ /**
+ * Should resorce bundles be reloaded.
+ *
+ * @param reloadBundles reload bundles?
+ */
+ @Inject(value = StrutsConstants.STRUTS_I18N_RELOAD, required = false)
+ public void setReloadBundles(String reloadBundles) {
+ this.reloadBundles = Boolean.parseBoolean(reloadBundles);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_DEVMODE, required = false)
+ public void setDevMode(String devMode) {
+ this.devMode = Boolean.parseBoolean(devMode);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false)
+ public void setCustomI18NResources(String bundles) {
+ if (bundles != null && bundles.length() > 0) {
+ StringTokenizer customBundles = new StringTokenizer(bundles, ", ");
+
+ while (customBundles.hasMoreTokens()) {
+ String name = customBundles.nextToken();
+ try {
+ LOG.trace("Loading global messages from [{}]", name);
+ addDefaultResourceBundle(name);
+ } catch (Exception e) {
+ LOG.error(new ParameterizedMessage("Could not find messages file {}.properties. Skipping", name), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Add's the bundle to the internal list of default bundles.
+ * <p>
+ * If the bundle already exists in the list it will be readded.
+ * </p>
+ *
+ * @param resourceBundleName the name of the bundle to add.
+ */
+ @Override
+ public void addDefaultResourceBundle(String resourceBundleName) {
+ //make sure this doesn't get added more than once
+ final ClassLoader ccl = getCurrentThreadContextClassLoader();
+ synchronized (XWORK_MESSAGES_BUNDLE) {
+ List<String> bundles = classLoaderMap.get(ccl.hashCode());
+ if (bundles == null) {
+ bundles = new CopyOnWriteArrayList<>();
+ classLoaderMap.put(ccl.hashCode(), bundles);
+ }
+ bundles.remove(resourceBundleName);
+ bundles.add(0, resourceBundleName);
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", resourceBundleName, ccl.toString());
+ }
+ }
+
+ /**
+ * Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale
+ * with language "en", country "US" and variant "foo". This will parse the output of
+ * {@link java.util.Locale#toString()}.
+ *
+ * @param localeStr The locale String to parse.
+ * @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
+ * @return requested Locale
+ *
+ * @deprecated please use {@link org.apache.commons.lang3.LocaleUtils#toLocale(String)}
+ */
+ @Deprecated
+ public static Locale localeFromString(String localeStr, Locale defaultLocale) {
+ if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) {
+ if (defaultLocale != null) {
+ return defaultLocale;
+ }
+ return Locale.getDefault();
+ }
+
+ int index = localeStr.indexOf('_');
+ if (index < 0) {
+ return new Locale(localeStr);
+ }
+
+ String language = localeStr.substring(0, index);
+ if (index == localeStr.length()) {
+ return new Locale(language);
+ }
+
+ localeStr = localeStr.substring(index + 1);
+ index = localeStr.indexOf('_');
+ if (index < 0) {
+ return new Locale(language, localeStr);
+ }
+
+ String country = localeStr.substring(0, index);
+ if (index == localeStr.length()) {
+ return new Locale(language, country);
+ }
+
+ localeStr = localeStr.substring(index + 1);
+ return new Locale(language, country, localeStr);
+ }
+
+ /**
+ * Returns a localized message for the specified key, aTextName. Neither the key nor the
+ * message is evaluated.
+ *
+ * @param aTextName the message key
+ * @param locale the locale the message should be for
+ * @return a localized message based on the specified key, or null if no localized message can be found for it
+ */
+ @Override
+ public String findDefaultText(String aTextName, Locale locale) {
+ List<String> localList = classLoaderMap.get(Thread.currentThread().getContextClassLoader().hashCode());
+
+ for (String bundleName : localList) {
+ ResourceBundle bundle = findResourceBundle(bundleName, locale);
+ if (bundle != null) {
+ reloadBundles();
+ try {
+ return bundle.getString(aTextName);
+ } catch (MissingResourceException e) {
+ // will be logged when not found in any bundle
+ }
+ }
+ }
+
+ if (devMode) {
+ LOG.warn("Missing key [{}] in bundles [{}]!", aTextName, localList);
+ } else {
+ LOG.debug("Missing key [{}] in bundles [{}]!", aTextName, localList);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a localized message for the specified key, aTextName, substituting variables from the
+ * array of params into the message. Neither the key nor the message is evaluated.
+ *
+ * @param aTextName the message key
+ * @param locale the locale the message should be for
+ * @param params an array of objects to be substituted into the message text
+ * @return A formatted message based on the specified key, or null if no localized message can be found for it
+ */
+ @Override
+ public String findDefaultText(String aTextName, Locale locale, Object[] params) {
+ String defaultText = findDefaultText(aTextName, locale);
+ if (defaultText != null) {
+ MessageFormat mf = buildMessageFormat(defaultText, locale);
+ return formatWithNullDetection(mf, params);
+ }
+ return null;
+ }
+
+ /**
+ * Finds the given resource bundle by it's name.
+ * <p>
+ * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
+ * </p>
+ *
+ * @param aBundleName the name of the bundle (usually it's FQN classname).
+ * @param locale the locale.
+ * @return the bundle, <tt>null</tt> if not found.
+ */
+ @Override
+ public ResourceBundle findResourceBundle(String aBundleName, Locale locale) {
+ ClassLoader classLoader = getCurrentThreadContextClassLoader();
+ String key = createMissesKey(String.valueOf(classLoader.hashCode()), aBundleName, locale);
+
+ if (missingBundles.contains(key)) {
+ return null;
+ }
+
+ ResourceBundle bundle = null;
+ try {
+ if (bundlesMap.containsKey(key)) {
+ bundle = bundlesMap.get(key);
+ } else {
+ bundle = ResourceBundle.getBundle(aBundleName, locale, classLoader);
+ bundlesMap.putIfAbsent(key, bundle);
+ }
+ } catch (MissingResourceException ex) {
+ if (delegatedClassLoaderMap.containsKey(classLoader.hashCode())) {
+ try {
+ if (bundlesMap.containsKey(key)) {
+ bundle = bundlesMap.get(key);
+ } else {
+ bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode()));
+ bundlesMap.putIfAbsent(key, bundle);
+ }
+ } catch (MissingResourceException e) {
+ LOG.debug("Missing resource bundle [{}]!", aBundleName, e);
+ missingBundles.add(key);
+ }
+ } else {
+ LOG.debug("Missing resource bundle [{}]!", aBundleName);
+ missingBundles.add(key);
+ }
+ }
+ return bundle;
+ }
+
+ /**
+ * @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader
+ */
+ public void setDelegatedClassLoader(final ClassLoader classLoader) {
+ synchronized (bundlesMap) {
+ delegatedClassLoaderMap.put(getCurrentThreadContextClassLoader().hashCode(), classLoader);
+ }
+ }
+
+ /**
+ * @param bundleName Removes the bundle from any cached "misses"
+ */
+ public void clearBundle(final String bundleName) {
+ bundlesMap.remove(getCurrentThreadContextClassLoader().hashCode() + bundleName);
+ }
+
+
+ /**
+ * Creates a key to used for lookup/storing in the bundle misses cache.
+ *
+ * @param prefix the prefix for the returning String - it is supposed to be the ClassLoader hash code.
+ * @param aBundleName the name of the bundle (usually it's FQN classname).
+ * @param locale the locale.
+ * @return the key to use for lookup/storing in the bundle misses cache.
+ */
+ private String createMissesKey(String prefix, String aBundleName, Locale locale) {
+ return prefix + aBundleName + "_" + locale.toString();
+ }
+
+ /**
+ * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
+ * with aTextName as the default message.
+ *
+ * @param aClass class name
+ * @param aTextName text name
+ * @param locale the locale
+ *
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
+ */
+ @Override
+ public String findText(Class aClass, String aTextName, Locale locale) {
+ return findText(aClass, aTextName, locale, aTextName, new Object[0]);
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName. Both the key and the message
+ * itself is evaluated as required. The following algorithm is used to find the requested
+ * message:
+ * </p>
+ *
+ * <ol>
+ * <li>Look for message in aClass' class hierarchy.
+ * <ol>
+ * <li>Look for the message in a resource bundle for aClass</li>
+ * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+ * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+ * </ol></li>
+ * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+ * the model's class hierarchy (repeat sub-steps listed above).</li>
+ * <li>If not found, look for message in child property. This is determined by evaluating
+ * the message key as an OGNL expression. For example, if the key is
+ * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+ * object. If so, repeat the entire process fromthe beginning with the object's class as
+ * aClass and "address.state" as the message key.</li>
+ * <li>If not found, look for the message in aClass' package hierarchy.</li>
+ * <li>If still not found, look for the message in the default resource bundles.</li>
+ * <li>Return defaultMessage</li>
+ * </ol>
+ *
+ * <p>
+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+ * message for that specific key cannot be found, the general form will also be looked up
+ * (i.e. user.phone[*]).
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * @param aClass the class whose name to use as the start point for the search
+ * @param aTextName the key to find the text message for
+ * @param locale the locale the message should be for
+ * @param defaultMessage the message to be returned if no text message can be found in any
+ * resource bundle
+ * @param args arguments
+ * resource bundle
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+ ValueStack valueStack = ActionContext.getContext().getValueStack();
+ return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
+
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName. Both the key and the message
+ * itself is evaluated as required. The following algorithm is used to find the requested
+ * message:
+ * </p>
+ *
+ * <ol>
+ * <li>Look for message in aClass' class hierarchy.
+ * <ol>
+ * <li>Look for the message in a resource bundle for aClass</li>
+ * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+ * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+ * </ol></li>
+ * <li>If not found and aClass is a {@link ModelDriven} Action, then look for message in
+ * the model's class hierarchy (repeat sub-steps listed above).</li>
+ * <li>If not found, look for message in child property. This is determined by evaluating
+ * the message key as an OGNL expression. For example, if the key is
+ * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+ * object. If so, repeat the entire process fromthe beginning with the object's class as
+ * aClass and "address.state" as the message key.</li>
+ * <li>If not found, look for the message in aClass' package hierarchy.</li>
+ * <li>If still not found, look for the message in the default resource bundles.</li>
+ * <li>Return defaultMessage</li>
+ * </ol>
+ *
+ * <p>
+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+ * message for that specific key cannot be found, the general form will also be looked up
+ * (i.e. user.phone[*]).
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * <p>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ * </p>
+ *
+ * @param aClass the class whose name to use as the start point for the search
+ * @param aTextName the key to find the text message for
+ * @param locale the locale the message should be for
+ * @param defaultMessage the message to be returned if no text message can be found in any
+ * resource bundle
+ * @param args arguments
+ * @param valueStack the value stack to use to evaluate expressions instead of the
+ * one in the ActionContext ThreadLocal
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args,
+ ValueStack valueStack) {
+ String indexedTextName = null;
+ if (aTextName == null) {
+ LOG.warn("Trying to find text with null key!");
+ aTextName = "";
+ }
+ // calculate indexedTextName (collection[*]) if applicable
+ if (aTextName.contains("[")) {
+ int i = -1;
+
+ indexedTextName = aTextName;
+
+ while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
+ int j = indexedTextName.indexOf("]", i);
+ String a = indexedTextName.substring(0, i);
+ String b = indexedTextName.substring(j);
+ indexedTextName = a + "[*" + b;
+ }
+ }
+
+ // search up class hierarchy
+ String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (ModelDriven.class.isAssignableFrom(aClass)) {
+ ActionContext context = ActionContext.getContext();
+ // search up model's class hierarchy
+ ActionInvocation actionInvocation = context.getActionInvocation();
+
+ // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null
+ if (actionInvocation != null) {
+ Object action = actionInvocation.getAction();
+ if (action instanceof ModelDriven) {
+ Object model = ((ModelDriven) action).getModel();
+ if (model != null) {
+ msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack);
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+ }
+ }
+
+ // nothing still? alright, search the package hierarchy now
+ for (Class clazz = aClass;
+ (clazz != null) && !clazz.equals(Object.class);
+ clazz = clazz.getSuperclass()) {
+
+ String basePackageName = clazz.getName();
+ while (basePackageName.lastIndexOf('.') != -1) {
+ basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.'));
+ String packageName = basePackageName + ".package";
+ msg = getMessage(packageName, locale, aTextName, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedTextName != null) {
+ msg = getMessage(packageName, locale, indexedTextName, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+ }
+
+ // see if it's a child property
+ int idx = aTextName.indexOf(".");
+
+ if (idx != -1) {
+ String newKey = null;
+ String prop = null;
+
+ if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
+ idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
+
+ if (idx != -1) {
+ prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
+ newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1);
+ }
+ } else {
+ prop = aTextName.substring(0, idx);
+ newKey = aTextName.substring(idx + 1);
+ }
+
+ if (prop != null) {
+ Object obj = valueStack.findValue(prop);
+ try {
+ Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot());
+ if (actionObj != null) {
+ PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop);
+
+ if (propertyDescriptor != null) {
+ Class clazz = propertyDescriptor.getPropertyType();
+
+ if (clazz != null) {
+ if (obj != null) {
+ valueStack.push(obj);
+ }
+ msg = findText(clazz, newKey, locale, null, args);
+ if (obj != null) {
+ valueStack.pop();
+ }
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.debug("unable to find property {}", prop, e);
+ }
+ }
+ }
+
+ // get default
+ GetDefaultMessageReturnArg result;
+ if (indexedTextName == null) {
+ result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+ } else {
+ result = getDefaultMessage(aTextName, locale, valueStack, args, null);
+ if (result != null && result.message != null) {
+ return result.message;
+ }
+ result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
+ }
+
+ // could we find the text, if not log a warn
+ if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
+ String warn = "Unable to find text for key '" + aTextName + "' ";
+ if (indexedTextName != null) {
+ warn += " or indexed key '" + indexedTextName + "' ";
+ }
+ warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
+ LOG.debug(warn);
+ }
+
+ return result != null ? result.message : null;
+ }
+
+ /**
+ * Determines if we found the text in the bundles.
+ *
+ * @param result the result so far
+ * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
+ */
+ private boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
+ if (result == null || result.message == null) {
+ return true;
+ }
+
+ // did we find it in the bundle, then no problem?
+ if (result.foundInBundle) {
+ return false;
+ }
+
+ // not found in bundle
+ return true;
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName, in the specified resource bundle
+ * with aTextName as the default message.
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * @param bundle a resource bundle name
+ * @param aTextName text name
+ * @param locale the locale
+ *
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
+ */
+ @Override
+ public String findText(ResourceBundle bundle, String aTextName, Locale locale) {
+ return findText(bundle, aTextName, locale, aTextName, new Object[0]);
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName, in the specified resource
+ * bundle.
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * <p>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ * </p>
+ *
+ * @param bundle the bundle
+ * @param aTextName the key
+ * @param locale the locale
+ * @param defaultMessage the default message to use if no message was found in the bundle
+ * @param args arguments for the message formatter.
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+ ValueStack valueStack = ActionContext.getContext().getValueStack();
+ return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
+ }
+
+ /**
+ * <p>
+ * Finds a localized text message for the given key, aTextName, in the specified resource
+ * bundle.
+ * </p>
+ *
+ * <p>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * </p>
+ *
+ * <p>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ * </p>
+ *
+ * @param bundle the bundle
+ * @param aTextName the key
+ * @param locale the locale
+ * @param defaultMessage the default message to use if no message was found in the bundle
+ * @param args arguments for the message formatter.
+ * @param valueStack the OGNL value stack.
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ @Override
+ public String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
+ ValueStack valueStack) {
+ try {
+ reloadBundles(valueStack.getContext());
+
+ String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
+ MessageFormat mf = buildMessageFormat(message, locale);
+
+ return formatWithNullDetection(mf, args);
+ } catch (MissingResourceException ex) {
+ if (devMode) {
+ LOG.warn("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+ } else {
+ LOG.debug("Missing key [{}] in bundle [{}]!", aTextName, bundle);
+ }
+ }
+
+ GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+ if (unableToFindTextForKey(result)) {
+ LOG.warn("Unable to find text for key '{}' in ResourceBundles for locale '{}'", aTextName, locale);
+ }
+ return result != null ? result.message : null;
+ }
+
+ /**
+ * @return the default message.
+ */
+ private GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
+ String defaultMessage) {
+ GetDefaultMessageReturnArg result = null;
+ boolean found = true;
+
+ if (key != null) {
+ String message = findDefaultText(key, locale);
+
+ if (message == null) {
+ message = defaultMessage;
+ found = false; // not found in bundles
+ }
+
+ // defaultMessage may be null
+ if (message != null) {
+ MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
+
+ String msg = formatWithNullDetection(mf, args);
+ result = new GetDefaultMessageReturnArg(msg, found);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * @return the message from the named resource bundle.
+ */
+ private String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
+ ResourceBundle bundle = findResourceBundle(bundleName, locale);
+ if (bundle == null) {
+ return null;
+ }
+ if (valueStack != null)
+ reloadBundles(valueStack.getContext());
+ try {
+ String message = bundle.getString(key);
+ if (valueStack != null)
+ message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
+ MessageFormat mf = buildMessageFormat(message, locale);
+ return formatWithNullDetection(mf, args);
+ } catch (MissingResourceException e) {
+ if (devMode) {
+ LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName);
+ } else {
+ LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName);
+ }
+ return null;
+ }
+ }
+
+ private String formatWithNullDetection(MessageFormat mf, Object[] args) {
+ String message = mf.format(args);
+ if ("null".equals(message)) {
+ return null;
+ } else {
+ return message;
+ }
+ }
+
+ private MessageFormat buildMessageFormat(String pattern, Locale locale) {
+ MessageFormatKey key = new MessageFormatKey(pattern, locale);
+ MessageFormat format = messageFormats.get(key);
+ if (format == null) {
+ format = new MessageFormat(pattern);
+ format.setLocale(locale);
+ format.applyPattern(pattern);
+ messageFormats.put(key, format);
+ }
+
+ return format;
+ }
+
+ /**
+ * Traverse up class hierarchy looking for message. Looks at class, then implemented interface,
+ * before going up hierarchy.
+ *
+ * @return the message
+ */
+ private String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
+ ValueStack valueStack) {
+ if (checked == null) {
+ checked = new TreeSet<String>();
+ } else if (checked.contains(clazz.getName())) {
+ return null;
+ }
+
+ // look in properties of this class
+ String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedKey != null) {
+ msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+
+ // look in properties of implemented interfaces
+ Class[] interfaces = clazz.getInterfaces();
+
+ for (Class anInterface : interfaces) {
+ msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedKey != null) {
+ msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+
+ // traverse up hierarchy
+ if (clazz.isInterface()) {
+ interfaces = clazz.getInterfaces();
+
+ for (Class anInterface : interfaces) {
+ msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ } else {
+ if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
+ return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
+ }
+ }
+
+ return null;
+ }
+
+ private void reloadBundles() {
+ reloadBundles(ActionContext.getContext() != null ? ActionContext.getContext().getContextMap() : null);
+ }
+
+ private void reloadBundles(Map<String, Object> context) {
+ if (reloadBundles) {
+ try {
+ Boolean reloaded;
+ if (context != null) {
+ reloaded = (Boolean) ObjectUtils.defaultIfNull(context.get(RELOADED), Boolean.FALSE);
+ }else {
+ reloaded = Boolean.FALSE;
+ }
+ if (!reloaded) {
+ bundlesMap.clear();
+ try {
+ clearMap(ResourceBundle.class, null, "cacheList");
+ } catch (NoSuchFieldException e) {
+ // happens in IBM JVM, that has a different ResourceBundle impl
+ // it has a 'cache' member
+ clearMap(ResourceBundle.class, null, "cache");
+ }
+
+ // now, for the true and utter hack, if we're running in tomcat, clear
+ // it's class loader resource cache as well.
+ clearTomcatCache();
+ if(context!=null) {
+ context.put(RELOADED, true);
+ }
+ LOG.debug("Resource bundles reloaded");
+ }
+ } catch (Exception e) {
+ LOG.error("Could not reload resource bundles", e);
+ }
+ }
+ }
+
+
+ private void clearTomcatCache() {
+ ClassLoader loader = getCurrentThreadContextClassLoader();
+ // no need for compilation here.
+ Class cl = loader.getClass();
+
+ try {
+ if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) {
+ clearMap(cl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
+ } else {
+ LOG.debug("Class loader {} is not tomcat loader.", cl.getName());
+ }
+ } catch (NoSuchFieldException nsfe) {
+ if ("org.apache.catalina.loader.WebappClassLoaderBase".equals(cl.getSuperclass().getName())) {
+ LOG.debug("Base class {} doesn't contain '{}' field, trying with parent!", cl.getName(), TOMCAT_RESOURCE_ENTRIES_FIELD, nsfe);
+ try {
+ clearMap(cl.getSuperclass(), loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
+ } catch (Exception e) {
+ LOG.warn("Couldn't clear tomcat cache using {}", cl.getSuperclass().getName(), e);
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Couldn't clear tomcat cache", cl.getName(), e);
+ }
+ }
+
+
+ private void clearMap(Class cl, Object obj, String name)
+ throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+
+ Field field = cl.getDeclaredField(name);
+ field.setAccessible(true);
+
+ Object cache = field.get(obj);
+
+ synchronized (cache) {
+ Class ccl = cache.getClass();
+ Method clearMethod = ccl.getMethod("clear");
+ clearMethod.invoke(cache);
+ }
+ }
+
+ /**
+ * Clears all the internal lists.
+ *
+ * @deprecated used only in tests
+ */
+ @Deprecated
+ public void reset() {
+ // no-op
+ }
+
+ static class MessageFormatKey {
+ String pattern;
+ Locale locale;
+
+ MessageFormatKey(String pattern, Locale locale) {
+ this.pattern = pattern;
+ this.locale = locale;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MessageFormatKey)) return false;
+
+ final MessageFormatKey messageFormatKey = (MessageFormatKey) o;
+
+ if (locale != null ? !locale.equals(messageFormatKey.locale) : messageFormatKey.locale != null)
+ return false;
+ if (pattern != null ? !pattern.equals(messageFormatKey.pattern) : messageFormatKey.pattern != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = (pattern != null ? pattern.hashCode() : 0);
+ result = 29 * result + (locale != null ? locale.hashCode() : 0);
+ return result;
+ }
+ }
+
+ private static ClassLoader getCurrentThreadContextClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ static class GetDefaultMessageReturnArg {
+ String message;
+ boolean foundInBundle;
+
+ public GetDefaultMessageReturnArg(String message, boolean foundInBundle) {
+ this.message = message;
+ this.foundInBundle = foundInBundle;
+ }
+ }
+
+ private static class EmptyResourceBundle extends ResourceBundle {
+ @Override
+ public Enumeration<String> getKeys() {
+ return null; // dummy
+ }
+
+ @Override
+ protected Object handleGetObject(String key) {
+ return null; // dummy
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/8bf77a1d/core/src/main/resources/struts-default.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index 362e317..ead2954 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -131,7 +131,7 @@
<bean type="com.opensymphony.xwork2.conversion.impl.StringConverter" name="struts" class="com.opensymphony.xwork2.conversion.impl.StringConverter" scope="singleton"/>
<bean type="com.opensymphony.xwork2.TextProvider" name="system" class="com.opensymphony.xwork2.DefaultTextProvider" scope="singleton" />
- <bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="struts" class="com.opensymphony.xwork2.util.DefaultLocalizedTextProvider" scope="singleton" />
+ <bean type="com.opensymphony.xwork2.LocalizedTextProvider" name="struts" class="com.opensymphony.xwork2.util.StrutsLocalizedTextProvider" scope="singleton" />
<bean type="com.opensymphony.xwork2.TextProviderFactory" name="struts" class="com.opensymphony.xwork2.StrutsTextProviderFactory" scope="singleton" />
<bean type="com.opensymphony.xwork2.LocaleProviderFactory" name="struts" class="com.opensymphony.xwork2.DefaultLocaleProviderFactory" scope="singleton" />
[05/10] struts git commit: WW-4762 Extracts base abstract class
Posted by lu...@apache.org.
WW-4762 Extracts base abstract class
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/2e23d7a0
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/2e23d7a0
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/2e23d7a0
Branch: refs/heads/master
Commit: 2e23d7a07ea4313136dc38b8de2451b04c882064
Parents: 8bf77a1
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 12:24:57 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 12:24:57 2017 +0200
----------------------------------------------------------------------
.../util/AbstractLocalizedTextProvider.java | 355 ++++++++++++++++++
.../util/StrutsLocalizedTextProvider.java | 368 +------------------
2 files changed, 363 insertions(+), 360 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/2e23d7a0/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
new file mode 100644
index 0000000..a2578bc
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -0,0 +1,355 @@
+package com.opensymphony.xwork2.util;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.LocalizedTextProvider;
+import com.opensymphony.xwork2.inject.Inject;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.StrutsConstants;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider {
+
+ private static final Logger LOG = LogManager.getLogger(AbstractLocalizedTextProvider.class);
+
+ public static final String XWORK_MESSAGES_BUNDLE = "com/opensymphony/xwork2/xwork-messages";
+ public static final String STRUTS_MESSAGES_BUNDLE = "org/apache/struts2/struts-messages";
+
+ private static final String TOMCAT_RESOURCE_ENTRIES_FIELD = "resourceEntries";
+ private final String RELOADED = "com.opensymphony.xwork2.util.LocalizedTextProvider.reloaded";
+
+ protected final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<>();
+ protected boolean devMode = false;
+ protected boolean reloadBundles = false;
+
+ private final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Integer, List<String>> classLoaderMap = new ConcurrentHashMap<>();
+ private final Set<String> missingBundles = Collections.synchronizedSet(new HashSet<String>());
+ private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>();
+
+ /**
+ * Add's the bundle to the internal list of default bundles.
+ * If the bundle already exists in the list it will be re-added.
+ *
+ * @param resourceBundleName the name of the bundle to add.
+ */
+ @Override
+ public void addDefaultResourceBundle(String resourceBundleName) {
+ //make sure this doesn't get added more than once
+ final ClassLoader ccl = getCurrentThreadContextClassLoader();
+ synchronized (XWORK_MESSAGES_BUNDLE) {
+ List<String> bundles = classLoaderMap.get(ccl.hashCode());
+ if (bundles == null) {
+ bundles = new CopyOnWriteArrayList<>();
+ classLoaderMap.put(ccl.hashCode(), bundles);
+ }
+ bundles.remove(resourceBundleName);
+ bundles.add(0, resourceBundleName);
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", resourceBundleName, ccl.toString());
+ }
+ }
+
+ protected List<String> getCurrentBundleNames() {
+ return classLoaderMap.get(getCurrentThreadContextClassLoader().hashCode());
+ }
+
+ protected ClassLoader getCurrentThreadContextClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ /**
+ * Returns a localized message for the specified key, aTextName. Neither the key nor the
+ * message is evaluated.
+ *
+ * @param aTextName the message key
+ * @param locale the locale the message should be for
+ * @return a localized message based on the specified key, or null if no localized message can be found for it
+ */
+ @Override
+ public String findDefaultText(String aTextName, Locale locale) {
+ List<String> localList = getCurrentBundleNames();
+
+ for (String bundleName : localList) {
+ ResourceBundle bundle = findResourceBundle(bundleName, locale);
+ if (bundle != null) {
+ reloadBundles();
+ try {
+ return bundle.getString(aTextName);
+ } catch (MissingResourceException e) {
+ // will be logged when not found in any bundle
+ }
+ }
+ }
+
+ if (devMode) {
+ LOG.warn("Missing key [{}] in bundles [{}]!", aTextName, localList);
+ } else {
+ LOG.debug("Missing key [{}] in bundles [{}]!", aTextName, localList);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a localized message for the specified key, aTextName, substituting variables from the
+ * array of params into the message. Neither the key nor the message is evaluated.
+ *
+ * @param aTextName the message key
+ * @param locale the locale the message should be for
+ * @param params an array of objects to be substituted into the message text
+ * @return A formatted message based on the specified key, or null if no localized message can be found for it
+ */
+ @Override
+ public String findDefaultText(String aTextName, Locale locale, Object[] params) {
+ String defaultText = findDefaultText(aTextName, locale);
+ if (defaultText != null) {
+ MessageFormat mf = buildMessageFormat(defaultText, locale);
+ return formatWithNullDetection(mf, params);
+ }
+ return null;
+ }
+
+ /**
+ * @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader
+ */
+ public void setDelegatedClassLoader(final ClassLoader classLoader) {
+ synchronized (bundlesMap) {
+ delegatedClassLoaderMap.put(getCurrentThreadContextClassLoader().hashCode(), classLoader);
+ }
+ }
+
+ /**
+ * @param bundleName Removes the bundle from any cached "misses"
+ */
+ public void clearBundle(final String bundleName) {
+ bundlesMap.remove(getCurrentThreadContextClassLoader().hashCode() + bundleName);
+ }
+
+ protected void reloadBundles() {
+ reloadBundles(ActionContext.getContext() != null ? ActionContext.getContext().getContextMap() : null);
+ }
+
+ protected void reloadBundles(Map<String, Object> context) {
+ if (reloadBundles) {
+ try {
+ Boolean reloaded;
+ if (context != null) {
+ reloaded = (Boolean) ObjectUtils.defaultIfNull(context.get(RELOADED), Boolean.FALSE);
+ } else {
+ reloaded = Boolean.FALSE;
+ }
+ if (!reloaded) {
+ bundlesMap.clear();
+ try {
+ clearMap(ResourceBundle.class, null, "cacheList");
+ } catch (NoSuchFieldException e) {
+ // happens in IBM JVM, that has a different ResourceBundle impl
+ // it has a 'cache' member
+ clearMap(ResourceBundle.class, null, "cache");
+ }
+
+ // now, for the true and utter hack, if we're running in tomcat, clear
+ // it's class loader resource cache as well.
+ clearTomcatCache();
+ if (context != null) {
+ context.put(RELOADED, true);
+ }
+ LOG.debug("Resource bundles reloaded");
+ }
+ } catch (Exception e) {
+ LOG.error("Could not reload resource bundles", e);
+ }
+ }
+ }
+
+ private void clearTomcatCache() {
+ ClassLoader loader = getCurrentThreadContextClassLoader();
+ // no need for compilation here.
+ Class cl = loader.getClass();
+
+ try {
+ if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) {
+ clearMap(cl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
+ } else {
+ LOG.debug("Class loader {} is not tomcat loader.", cl.getName());
+ }
+ } catch (NoSuchFieldException nsfe) {
+ if ("org.apache.catalina.loader.WebappClassLoaderBase".equals(cl.getSuperclass().getName())) {
+ LOG.debug("Base class {} doesn't contain '{}' field, trying with parent!", cl.getName(), TOMCAT_RESOURCE_ENTRIES_FIELD, nsfe);
+ try {
+ clearMap(cl.getSuperclass(), loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
+ } catch (Exception e) {
+ LOG.warn("Couldn't clear tomcat cache using {}", cl.getSuperclass().getName(), e);
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Couldn't clear tomcat cache", cl.getName(), e);
+ }
+ }
+
+ private void clearMap(Class cl, Object obj, String name)
+ throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+
+ Field field = cl.getDeclaredField(name);
+ field.setAccessible(true);
+
+ Object cache = field.get(obj);
+
+ synchronized (cache) {
+ Class ccl = cache.getClass();
+ Method clearMethod = ccl.getMethod("clear");
+ clearMethod.invoke(cache);
+ }
+ }
+
+ protected MessageFormat buildMessageFormat(String pattern, Locale locale) {
+ MessageFormatKey key = new MessageFormatKey(pattern, locale);
+ MessageFormat format = messageFormats.get(key);
+ if (format == null) {
+ format = new MessageFormat(pattern);
+ format.setLocale(locale);
+ format.applyPattern(pattern);
+ messageFormats.put(key, format);
+ }
+
+ return format;
+ }
+
+ protected String formatWithNullDetection(MessageFormat mf, Object[] args) {
+ String message = mf.format(args);
+ if ("null".equals(message)) {
+ return null;
+ } else {
+ return message;
+ }
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_I18N_RELOAD, required = false)
+ public void setReloadBundles(String reloadBundles) {
+ this.reloadBundles = Boolean.parseBoolean(reloadBundles);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_DEVMODE, required = false)
+ public void setDevMode(String devMode) {
+ this.devMode = Boolean.parseBoolean(devMode);
+ }
+
+ /**
+ * Finds the given resource bundle by it's name.
+ * <p>
+ * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
+ * </p>
+ *
+ * @param aBundleName the name of the bundle (usually it's FQN classname).
+ * @param locale the locale.
+ * @return the bundle, <tt>null</tt> if not found.
+ */
+ @Override
+ public ResourceBundle findResourceBundle(String aBundleName, Locale locale) {
+ ClassLoader classLoader = getCurrentThreadContextClassLoader();
+ String key = createMissesKey(String.valueOf(classLoader.hashCode()), aBundleName, locale);
+
+ if (missingBundles.contains(key)) {
+ return null;
+ }
+
+ ResourceBundle bundle = null;
+ try {
+ if (bundlesMap.containsKey(key)) {
+ bundle = bundlesMap.get(key);
+ } else {
+ bundle = ResourceBundle.getBundle(aBundleName, locale, classLoader);
+ bundlesMap.putIfAbsent(key, bundle);
+ }
+ } catch (MissingResourceException ex) {
+ if (delegatedClassLoaderMap.containsKey(classLoader.hashCode())) {
+ try {
+ if (bundlesMap.containsKey(key)) {
+ bundle = bundlesMap.get(key);
+ } else {
+ bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode()));
+ bundlesMap.putIfAbsent(key, bundle);
+ }
+ } catch (MissingResourceException e) {
+ LOG.debug("Missing resource bundle [{}]!", aBundleName, e);
+ missingBundles.add(key);
+ }
+ } else {
+ LOG.debug("Missing resource bundle [{}]!", aBundleName);
+ missingBundles.add(key);
+ }
+ }
+ return bundle;
+ }
+
+ /**
+ * Creates a key to used for lookup/storing in the bundle misses cache.
+ *
+ * @param prefix the prefix for the returning String - it is supposed to be the ClassLoader hash code.
+ * @param aBundleName the name of the bundle (usually it's FQN classname).
+ * @param locale the locale.
+ * @return the key to use for lookup/storing in the bundle misses cache.
+ */
+ private String createMissesKey(String prefix, String aBundleName, Locale locale) {
+ return prefix + aBundleName + "_" + locale.toString();
+ }
+
+ static class MessageFormatKey {
+ String pattern;
+ Locale locale;
+
+ MessageFormatKey(String pattern, Locale locale) {
+ this.pattern = pattern;
+ this.locale = locale;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MessageFormatKey that = (MessageFormatKey) o;
+
+ if (pattern != null ? !pattern.equals(that.pattern) : that.pattern != null) return false;
+ return locale != null ? locale.equals(that.locale) : that.locale == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = pattern != null ? pattern.hashCode() : 0;
+ result = 31 * result + (locale != null ? locale.hashCode() : 0);
+ return result;
+ }
+ }
+
+ static class GetDefaultMessageReturnArg {
+ String message;
+ boolean foundInBundle;
+
+ public GetDefaultMessageReturnArg(String message, boolean foundInBundle) {
+ this.message = message;
+ this.foundInBundle = foundInBundle;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/2e23d7a0/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
index 78bf08d..3d03f9a 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java
@@ -23,52 +23,32 @@ package com.opensymphony.xwork2.util;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
-import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.reflection.ReflectionProviderFactory;
-import org.apache.commons.lang3.ObjectUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.struts2.StrutsConstants;
import java.beans.PropertyDescriptor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.text.MessageFormat;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
/**
* Provides support for localization in the framework, it can be used to read only default bundles,
* or it can search the class hierarchy to find proper bundles.
*/
-public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
+public class StrutsLocalizedTextProvider extends AbstractLocalizedTextProvider {
private static final Logger LOG = LogManager.getLogger(StrutsLocalizedTextProvider.class);
- public static final String XWORK_MESSAGES_BUNDLE = "com/opensymphony/xwork2/xwork-messages";
- public static final String STRUTS_MESSAGES_BUNDLE = "org/apache/struts2/struts-messages";
-
- private static final String TOMCAT_RESOURCE_ENTRIES_FIELD = "resourceEntries";
-
- private final ConcurrentMap<Integer, List<String>> classLoaderMap = new ConcurrentHashMap<>();
-
- private boolean reloadBundles = false;
- private boolean devMode = false;
-
- private final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<>();
- private final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<>();
- private final ConcurrentMap<Integer, ClassLoader> delegatedClassLoaderMap = new ConcurrentHashMap<>();
- private final Set<String> missingBundles = Collections.synchronizedSet(new HashSet<String>());
-
- private final String RELOADED = "com.opensymphony.xwork2.util.LocalizedTextUtil.reloaded";
-
/**
* Clears the internal list of resource bundles.
*
@@ -80,26 +60,10 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
}
public StrutsLocalizedTextProvider() {
-
addDefaultResourceBundle(XWORK_MESSAGES_BUNDLE);
addDefaultResourceBundle(STRUTS_MESSAGES_BUNDLE);
}
- /**
- * Should resorce bundles be reloaded.
- *
- * @param reloadBundles reload bundles?
- */
- @Inject(value = StrutsConstants.STRUTS_I18N_RELOAD, required = false)
- public void setReloadBundles(String reloadBundles) {
- this.reloadBundles = Boolean.parseBoolean(reloadBundles);
- }
-
- @Inject(value = StrutsConstants.STRUTS_DEVMODE, required = false)
- public void setDevMode(String devMode) {
- this.devMode = Boolean.parseBoolean(devMode);
- }
-
@Inject(value = StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, required = false)
public void setCustomI18NResources(String bundles) {
if (bundles != null && bundles.length() > 0) {
@@ -118,33 +82,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
}
/**
- * Add's the bundle to the internal list of default bundles.
- * <p>
- * If the bundle already exists in the list it will be readded.
- * </p>
- *
- * @param resourceBundleName the name of the bundle to add.
- */
- @Override
- public void addDefaultResourceBundle(String resourceBundleName) {
- //make sure this doesn't get added more than once
- final ClassLoader ccl = getCurrentThreadContextClassLoader();
- synchronized (XWORK_MESSAGES_BUNDLE) {
- List<String> bundles = classLoaderMap.get(ccl.hashCode());
- if (bundles == null) {
- bundles = new CopyOnWriteArrayList<>();
- classLoaderMap.put(ccl.hashCode(), bundles);
- }
- bundles.remove(resourceBundleName);
- bundles.add(0, resourceBundleName);
- }
-
- if (LOG.isDebugEnabled()) {
- LOG.debug("Added default resource bundle '{}' to default resource bundles for the following classloader '{}'", resourceBundleName, ccl.toString());
- }
- }
-
- /**
* Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale
* with language "en", country "US" and variant "foo". This will parse the output of
* {@link java.util.Locale#toString()}.
@@ -190,135 +127,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
}
/**
- * Returns a localized message for the specified key, aTextName. Neither the key nor the
- * message is evaluated.
- *
- * @param aTextName the message key
- * @param locale the locale the message should be for
- * @return a localized message based on the specified key, or null if no localized message can be found for it
- */
- @Override
- public String findDefaultText(String aTextName, Locale locale) {
- List<String> localList = classLoaderMap.get(Thread.currentThread().getContextClassLoader().hashCode());
-
- for (String bundleName : localList) {
- ResourceBundle bundle = findResourceBundle(bundleName, locale);
- if (bundle != null) {
- reloadBundles();
- try {
- return bundle.getString(aTextName);
- } catch (MissingResourceException e) {
- // will be logged when not found in any bundle
- }
- }
- }
-
- if (devMode) {
- LOG.warn("Missing key [{}] in bundles [{}]!", aTextName, localList);
- } else {
- LOG.debug("Missing key [{}] in bundles [{}]!", aTextName, localList);
- }
-
- return null;
- }
-
- /**
- * Returns a localized message for the specified key, aTextName, substituting variables from the
- * array of params into the message. Neither the key nor the message is evaluated.
- *
- * @param aTextName the message key
- * @param locale the locale the message should be for
- * @param params an array of objects to be substituted into the message text
- * @return A formatted message based on the specified key, or null if no localized message can be found for it
- */
- @Override
- public String findDefaultText(String aTextName, Locale locale, Object[] params) {
- String defaultText = findDefaultText(aTextName, locale);
- if (defaultText != null) {
- MessageFormat mf = buildMessageFormat(defaultText, locale);
- return formatWithNullDetection(mf, params);
- }
- return null;
- }
-
- /**
- * Finds the given resource bundle by it's name.
- * <p>
- * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
- * </p>
- *
- * @param aBundleName the name of the bundle (usually it's FQN classname).
- * @param locale the locale.
- * @return the bundle, <tt>null</tt> if not found.
- */
- @Override
- public ResourceBundle findResourceBundle(String aBundleName, Locale locale) {
- ClassLoader classLoader = getCurrentThreadContextClassLoader();
- String key = createMissesKey(String.valueOf(classLoader.hashCode()), aBundleName, locale);
-
- if (missingBundles.contains(key)) {
- return null;
- }
-
- ResourceBundle bundle = null;
- try {
- if (bundlesMap.containsKey(key)) {
- bundle = bundlesMap.get(key);
- } else {
- bundle = ResourceBundle.getBundle(aBundleName, locale, classLoader);
- bundlesMap.putIfAbsent(key, bundle);
- }
- } catch (MissingResourceException ex) {
- if (delegatedClassLoaderMap.containsKey(classLoader.hashCode())) {
- try {
- if (bundlesMap.containsKey(key)) {
- bundle = bundlesMap.get(key);
- } else {
- bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoaderMap.get(classLoader.hashCode()));
- bundlesMap.putIfAbsent(key, bundle);
- }
- } catch (MissingResourceException e) {
- LOG.debug("Missing resource bundle [{}]!", aBundleName, e);
- missingBundles.add(key);
- }
- } else {
- LOG.debug("Missing resource bundle [{}]!", aBundleName);
- missingBundles.add(key);
- }
- }
- return bundle;
- }
-
- /**
- * @param classLoader a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader
- */
- public void setDelegatedClassLoader(final ClassLoader classLoader) {
- synchronized (bundlesMap) {
- delegatedClassLoaderMap.put(getCurrentThreadContextClassLoader().hashCode(), classLoader);
- }
- }
-
- /**
- * @param bundleName Removes the bundle from any cached "misses"
- */
- public void clearBundle(final String bundleName) {
- bundlesMap.remove(getCurrentThreadContextClassLoader().hashCode() + bundleName);
- }
-
-
- /**
- * Creates a key to used for lookup/storing in the bundle misses cache.
- *
- * @param prefix the prefix for the returning String - it is supposed to be the ClassLoader hash code.
- * @param aBundleName the name of the bundle (usually it's FQN classname).
- * @param locale the locale.
- * @return the key to use for lookup/storing in the bundle misses cache.
- */
- private String createMissesKey(String prefix, String aBundleName, Locale locale) {
- return prefix + aBundleName + "_" + locale.toString();
- }
-
- /**
* Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
* with aTextName as the default message.
*
@@ -759,28 +567,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
}
}
- private String formatWithNullDetection(MessageFormat mf, Object[] args) {
- String message = mf.format(args);
- if ("null".equals(message)) {
- return null;
- } else {
- return message;
- }
- }
-
- private MessageFormat buildMessageFormat(String pattern, Locale locale) {
- MessageFormatKey key = new MessageFormatKey(pattern, locale);
- MessageFormat format = messageFormats.get(key);
- if (format == null) {
- format = new MessageFormat(pattern);
- format.setLocale(locale);
- format.applyPattern(pattern);
- messageFormats.put(key, format);
- }
-
- return format;
- }
-
/**
* Traverse up class hierarchy looking for message. Looks at class, then implemented interface,
* before going up hierarchy.
@@ -790,7 +576,7 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
private String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
ValueStack valueStack) {
if (checked == null) {
- checked = new TreeSet<String>();
+ checked = new TreeSet<>();
} else if (checked.contains(clazz.getName())) {
return null;
}
@@ -849,85 +635,6 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
return null;
}
- private void reloadBundles() {
- reloadBundles(ActionContext.getContext() != null ? ActionContext.getContext().getContextMap() : null);
- }
-
- private void reloadBundles(Map<String, Object> context) {
- if (reloadBundles) {
- try {
- Boolean reloaded;
- if (context != null) {
- reloaded = (Boolean) ObjectUtils.defaultIfNull(context.get(RELOADED), Boolean.FALSE);
- }else {
- reloaded = Boolean.FALSE;
- }
- if (!reloaded) {
- bundlesMap.clear();
- try {
- clearMap(ResourceBundle.class, null, "cacheList");
- } catch (NoSuchFieldException e) {
- // happens in IBM JVM, that has a different ResourceBundle impl
- // it has a 'cache' member
- clearMap(ResourceBundle.class, null, "cache");
- }
-
- // now, for the true and utter hack, if we're running in tomcat, clear
- // it's class loader resource cache as well.
- clearTomcatCache();
- if(context!=null) {
- context.put(RELOADED, true);
- }
- LOG.debug("Resource bundles reloaded");
- }
- } catch (Exception e) {
- LOG.error("Could not reload resource bundles", e);
- }
- }
- }
-
-
- private void clearTomcatCache() {
- ClassLoader loader = getCurrentThreadContextClassLoader();
- // no need for compilation here.
- Class cl = loader.getClass();
-
- try {
- if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) {
- clearMap(cl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
- } else {
- LOG.debug("Class loader {} is not tomcat loader.", cl.getName());
- }
- } catch (NoSuchFieldException nsfe) {
- if ("org.apache.catalina.loader.WebappClassLoaderBase".equals(cl.getSuperclass().getName())) {
- LOG.debug("Base class {} doesn't contain '{}' field, trying with parent!", cl.getName(), TOMCAT_RESOURCE_ENTRIES_FIELD, nsfe);
- try {
- clearMap(cl.getSuperclass(), loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
- } catch (Exception e) {
- LOG.warn("Couldn't clear tomcat cache using {}", cl.getSuperclass().getName(), e);
- }
- }
- } catch (Exception e) {
- LOG.warn("Couldn't clear tomcat cache", cl.getName(), e);
- }
- }
-
-
- private void clearMap(Class cl, Object obj, String name)
- throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
-
- Field field = cl.getDeclaredField(name);
- field.setAccessible(true);
-
- Object cache = field.get(obj);
-
- synchronized (cache) {
- Class ccl = cache.getClass();
- Method clearMethod = ccl.getMethod("clear");
- clearMethod.invoke(cache);
- }
- }
-
/**
* Clears all the internal lists.
*
@@ -938,63 +645,4 @@ public class StrutsLocalizedTextProvider implements LocalizedTextProvider {
// no-op
}
- static class MessageFormatKey {
- String pattern;
- Locale locale;
-
- MessageFormatKey(String pattern, Locale locale) {
- this.pattern = pattern;
- this.locale = locale;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof MessageFormatKey)) return false;
-
- final MessageFormatKey messageFormatKey = (MessageFormatKey) o;
-
- if (locale != null ? !locale.equals(messageFormatKey.locale) : messageFormatKey.locale != null)
- return false;
- if (pattern != null ? !pattern.equals(messageFormatKey.pattern) : messageFormatKey.pattern != null)
- return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result;
- result = (pattern != null ? pattern.hashCode() : 0);
- result = 29 * result + (locale != null ? locale.hashCode() : 0);
- return result;
- }
- }
-
- private static ClassLoader getCurrentThreadContextClassLoader() {
- return Thread.currentThread().getContextClassLoader();
- }
-
- static class GetDefaultMessageReturnArg {
- String message;
- boolean foundInBundle;
-
- public GetDefaultMessageReturnArg(String message, boolean foundInBundle) {
- this.message = message;
- this.foundInBundle = foundInBundle;
- }
- }
-
- private static class EmptyResourceBundle extends ResourceBundle {
- @Override
- public Enumeration<String> getKeys() {
- return null; // dummy
- }
-
- @Override
- protected Object handleGetObject(String key) {
- return null; // dummy
- }
- }
-
}
[02/10] struts git commit: WW-4762 Drops unused imports
Posted by lu...@apache.org.
WW-4762 Drops unused imports
Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/3ffc9b84
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/3ffc9b84
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/3ffc9b84
Branch: refs/heads/master
Commit: 3ffc9b8450845d7a90a623607e3b4bb05888e2f9
Parents: 45d1a49
Author: Lukasz Lenart <lu...@apache.org>
Authored: Wed Apr 26 11:56:09 2017 +0200
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Wed Apr 26 11:56:09 2017 +0200
----------------------------------------------------------------------
.../java/com/opensymphony/xwork2/DefaultTextProvider.java | 8 +-------
.../main/java/org/apache/struts2/dispatcher/Dispatcher.java | 1 -
.../struts2/config/PropertiesConfigurationProviderTest.java | 1 -
3 files changed, 1 insertion(+), 9 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/struts/blob/3ffc9b84/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
index 51ed449..116a0a1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/DefaultTextProvider.java
@@ -16,7 +16,6 @@
package com.opensymphony.xwork2;
import com.opensymphony.xwork2.inject.Inject;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
import com.opensymphony.xwork2.util.ValueStack;
import java.io.Serializable;
@@ -27,12 +26,7 @@ import java.util.List;
import java.util.ResourceBundle;
/**
- * DefaultTextProvider gets texts from only the default resource bundles associated with the
- * LocalizedTextUtil.
- *
- * @author Jason Carreira jcarreira@gmail.com
- * @author Rainer Hermanns
- * @see DefaultLocalizedTextProvider#addDefaultResourceBundle(String)
+ * DefaultTextProvider gets texts from only the default resource bundles associated with the default bundles.
*/
public class DefaultTextProvider implements TextProvider, Serializable, Unchainable {
http://git-wip-us.apache.org/repos/asf/struts/blob/3ffc9b84/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
index 667f37d..55707a4 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
@@ -32,7 +32,6 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
http://git-wip-us.apache.org/repos/asf/struts/blob/3ffc9b84/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java b/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
index b4fd408..a82a8c6 100644
--- a/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
+++ b/core/src/test/java/org/apache/struts2/config/PropertiesConfigurationProviderTest.java
@@ -23,7 +23,6 @@ package org.apache.struts2.config;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
-import com.opensymphony.xwork2.util.DefaultLocalizedTextProvider;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import junit.framework.TestCase;
import org.apache.commons.lang3.LocaleUtils;