diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java
index e7a9bc7d3..d4665d778 100644
--- a/core/src/main/java/org/apache/struts2/StrutsConstants.java
+++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java
@@ -247,4 +247,6 @@ public final class StrutsConstants {
public static final String STRUTS_CONVERTER_CREATOR = "struts.converter.creator";
public static final String STRUTS_CONVERTER_HOLDER = "struts..converter.holder";
+ public static final String STRUTS_EXPRESSION_PARSER = "struts.expression.parser";
+
}
diff --git a/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java
index ddd1c5e0d..e3cf0db6f 100644
--- a/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java
+++ b/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java
@@ -49,10 +49,7 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Context;
import com.opensymphony.xwork2.inject.Factory;
import com.opensymphony.xwork2.inject.Scope;
-import com.opensymphony.xwork2.util.ClassLoaderUtil;
-import com.opensymphony.xwork2.util.LocalizedTextUtil;
-import com.opensymphony.xwork2.util.PatternMatcher;
-import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.*;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
@@ -276,6 +273,13 @@ import java.util.StringTokenizer;
*
singleton |
* Holds user converters' instances |
*
+ *
+ * | com.opensymphony.xwork2.util.TextParser |
+ * struts.expression.parser |
+ * singleton |
+ * Used to parse expressions like ${foo.bar} or %{bar.foo} but it is up tp the TextParser's
+ * implementation what kind of opening char to use (#, $, %, etc) |
+ *
*
*
*
@@ -355,6 +359,8 @@ public class BeanSelectionProvider implements ConfigurationProvider {
alias(UnknownHandlerManager.class, StrutsConstants.STRUTS_UNKNOWN_HANDLER_MANAGER, builder, props);
alias(UrlHelper.class, StrutsConstants.STRUTS_URL_HELPER, builder, props);
+ alias(TextParser.class, StrutsConstants.STRUTS_EXPRESSION_PARSER, builder, props);
+
if ("true".equalsIgnoreCase(props.getProperty(StrutsConstants.STRUTS_DEVMODE))) {
props.setProperty(StrutsConstants.STRUTS_I18N_RELOAD, "true");
props.setProperty(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, "true");
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/StrutsResultSupport.java b/core/src/main/java/org/apache/struts2/dispatcher/StrutsResultSupport.java
index 16dfc16db..676b0b916 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/StrutsResultSupport.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/StrutsResultSupport.java
@@ -197,13 +197,13 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics {
if (parse && param != null && invocation != null) {
return TextParseUtil.translateVariables(param, invocation.getStack(),
new TextParseUtil.ParsedValueEvaluator() {
- public Object evaluate(Object parsedValue) {
+ public Object evaluate(String parsedValue) {
if (encode) {
if (parsedValue != null) {
try {
// use UTF-8 as this is the recommended encoding by W3C to
// avoid incompatibilities.
- return URLEncoder.encode(parsedValue.toString(), "UTF-8");
+ return URLEncoder.encode(parsedValue, "UTF-8");
}
catch(UnsupportedEncodingException e) {
if (LOG.isWarnEnabled()) {
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index 570ac6828..4f2d69aae 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -100,6 +100,8 @@
+
+
diff --git a/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java b/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java
index fcbb7bd43..2fde6f7b1 100644
--- a/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java
+++ b/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java
@@ -25,6 +25,8 @@ import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.TextParser;
+import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
@@ -275,6 +277,8 @@ public class EmbeddedJSPResultTest extends TestCase {
//mock container
Container container = EasyMock.createNiceMock(Container.class);
EasyMock.expect(container.getInstance(XWorkConverter.class)).andReturn(converter).anyTimes();
+ TextParser parser = new OgnlTextParser();
+ EasyMock.expect(container.getInstance(TextParser.class)).andReturn(parser).anyTimes();
EasyMock.expect(container.getInstanceNames(FileManager.class)).andReturn(new HashSet()).anyTimes();
EasyMock.expect(container.getInstance(FileManager.class)).andReturn(fileManager).anyTimes();
diff --git a/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/AbstractTest.java b/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/AbstractTest.java
index 58489f644..23064b1af 100644
--- a/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/AbstractTest.java
+++ b/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/AbstractTest.java
@@ -24,10 +24,12 @@ package org.apache.struts2.views.java.simple;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.TextParser;
+import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.ValueStack;
import junit.framework.TestCase;
-import org.apache.struts2.StrutsConstants;
import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.StrutsConstants;
import org.apache.struts2.components.Component;
import org.apache.struts2.components.UIBean;
import org.apache.struts2.components.template.Template;
@@ -107,6 +109,8 @@ public abstract class AbstractTest extends TestCase {
XWorkConverter converter = new ConverterEx();
EasyMock.expect(container.getInstance(String.class, StrutsConstants.STRUTS_TAG_ALTSYNTAX)).andReturn("true").anyTimes();
EasyMock.expect(container.getInstance(XWorkConverter.class)).andReturn(converter).anyTimes();
+ TextParser parser = new OgnlTextParser();
+ EasyMock.expect(container.getInstance(TextParser.class)).andReturn(parser).anyTimes();
stackContext.put(ActionContext.CONTAINER, container);
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
index ca8074099..495165093 100644
--- a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java
@@ -52,10 +52,7 @@ import com.opensymphony.xwork2.ognl.accessor.XWorkIteratorPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkListPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
-import com.opensymphony.xwork2.util.CompoundRoot;
-import com.opensymphony.xwork2.util.PatternMatcher;
-import com.opensymphony.xwork2.util.ValueStackFactory;
-import com.opensymphony.xwork2.util.WildcardHelper;
+import com.opensymphony.xwork2.util.*;
import com.opensymphony.xwork2.util.fs.DefaultFileManager;
import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
@@ -137,6 +134,8 @@ public class XWorkConfigurationProvider implements ConfigurationProvider {
.factory(MethodAccessor.class, Object.class.getName(), XWorkMethodAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
+ .factory(TextParser.class, OgnlTextParser.class, Scope.SINGLETON)
+
.factory(NullHandler.class, Object.class.getName(), InstantiatingNullHandler.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, AnnotationActionValidatorManager.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON)
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
index 347659101..24df16995 100644
--- a/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
@@ -332,7 +332,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{
"Unexpected Exception caught setting '" + name + "' on '" + action.getClass() + ": " + e.getMessage()
});
- LOG.error(developerNotification);
+ LOG.error(developerNotification, e);
if (action instanceof ValidationAware) {
((ValidationAware) action).addActionMessage(developerNotification);
}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/OgnlTextParser.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/OgnlTextParser.java
new file mode 100644
index 000000000..c25298adb
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/OgnlTextParser.java
@@ -0,0 +1,84 @@
+package com.opensymphony.xwork2.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * OGNL implementation of {@link TextParser}
+ */
+public class OgnlTextParser implements TextParser {
+
+ public Object evaluate(char[] openChars, String expression, TextParseUtil.ParsedValueEvaluator evaluator, int maxLoopCount) {
+ // deal with the "pure" expressions first!
+ //expression = expression.trim();
+ Object result = expression;
+ for (char open : openChars) {
+ int loopCount = 1;
+ int pos = 0;
+
+ //this creates an implicit StringBuffer and shouldn't be used in the inner loop
+ final String lookupChars = open + "{";
+
+ while (true) {
+ int start = expression.indexOf(lookupChars, pos);
+ if (start == -1) {
+ pos = 0;
+ loopCount++;
+ start = expression.indexOf(lookupChars);
+ }
+ if (loopCount > maxLoopCount) {
+ // translateVariables prevent infinite loop / expression recursive evaluation
+ break;
+ }
+ int length = expression.length();
+ int x = start + 2;
+ int end;
+ char c;
+ int count = 1;
+ while (start != -1 && x < length && count != 0) {
+ c = expression.charAt(x++);
+ if (c == '{') {
+ count++;
+ } else if (c == '}') {
+ count--;
+ }
+ }
+ end = x - 1;
+
+ if ((start != -1) && (end != -1) && (count == 0)) {
+ String var = expression.substring(start + 2, end);
+
+ Object o = evaluator.evaluate(var);
+
+ String left = expression.substring(0, start);
+ String right = expression.substring(end + 1);
+ String middle = null;
+ if (o != null) {
+ middle = o.toString();
+ if (StringUtils.isEmpty(left)) {
+ result = o;
+ } else {
+ result = left.concat(middle);
+ }
+
+ if (StringUtils.isNotEmpty(right)) {
+ result = result.toString().concat(right);
+ }
+
+ expression = left.concat(middle).concat(right);
+ } else {
+ // the variable doesn't exist, so don't display anything
+ expression = left.concat(right);
+ result = expression;
+ }
+ pos = (left != null && left.length() > 0 ? left.length() - 1: 0) +
+ (middle != null && middle.length() > 0 ? middle.length() - 1: 0) +
+ 1;
+ pos = Math.max(pos, 1);
+ } else {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
index 0d7427558..07dccfbdd 100644
--- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
@@ -22,8 +22,6 @@ import com.opensymphony.xwork2.inject.Container;
import java.util.HashSet;
import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-
/**
* Utility class for text parsing.
@@ -153,84 +151,26 @@ public class TextParseUtil {
* @param evaluator
* @return Converted object from variable translation.
*/
- public static Object translateVariables(char[] openChars, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator, int maxLoopCount) {
- // deal with the "pure" expressions first!
- //expression = expression.trim();
- Object result = expression;
- for (char open : openChars) {
- int loopCount = 1;
- int pos = 0;
+ public static Object translateVariables(char[] openChars, String expression, final ValueStack stack, final Class asType, final ParsedValueEvaluator evaluator, int maxLoopCount) {
- //this creates an implicit StringBuffer and shouldn't be used in the inner loop
- final String lookupChars = open + "{";
- while (true) {
- int start = expression.indexOf(lookupChars, pos);
- if (start == -1) {
- pos = 0;
- loopCount++;
- start = expression.indexOf(lookupChars);
- }
- if (loopCount > maxLoopCount) {
- // translateVariables prevent infinite loop / expression recursive evaluation
- break;
- }
- int length = expression.length();
- int x = start + 2;
- int end;
- char c;
- int count = 1;
- while (start != -1 && x < length && count != 0) {
- c = expression.charAt(x++);
- if (c == '{') {
- count++;
- } else if (c == '}') {
- count--;
- }
- }
- end = x - 1;
-
- if ((start != -1) && (end != -1) && (count == 0)) {
- String var = expression.substring(start + 2, end);
-
- Object o = stack.findValue(var, asType);
- if (evaluator != null) {
- o = evaluator.evaluate(o);
- }
-
-
- String left = expression.substring(0, start);
- String right = expression.substring(end + 1);
- String middle = null;
- if (o != null) {
- middle = o.toString();
- if (StringUtils.isEmpty(left)) {
- result = o;
- } else {
- result = left.concat(middle);
- }
-
- if (StringUtils.isNotEmpty(right)) {
- result = result.toString().concat(right);
- }
-
- expression = left.concat(middle).concat(right);
- } else {
- // the variable doesn't exist, so don't display anything
- expression = left.concat(right);
- result = expression;
- }
- pos = (left != null && left.length() > 0 ? left.length() - 1: 0) +
- (middle != null && middle.length() > 0 ? middle.length() - 1: 0) +
- 1;
- pos = Math.max(pos, 1);
- } else {
- break;
+ ParsedValueEvaluator ognlEval = new ParsedValueEvaluator() {
+ public Object evaluate(String parsedValue) {
+ Object o = stack.findValue(parsedValue, asType);
+ if (evaluator != null) {
+ o = evaluator.evaluate(o.toString());
}
+ return o;
}
- }
+ };
+
+
+ TextParser parser = ((Container)stack.getContext().get(ActionContext.CONTAINER)).getInstance(TextParser.class);
XWorkConverter conv = ((Container)stack.getContext().get(ActionContext.CONTAINER)).getInstance(XWorkConverter.class);
+
+ Object result = parser.evaluate(openChars, expression, ognlEval, maxLoopCount);
+
return conv.convertValue(stack.getContext(), result, asType);
}
@@ -279,6 +219,6 @@ public class TextParseUtil {
* @param parsedValue - value parsed by ognl value stack
* @return return the evaluted value.
*/
- Object evaluate(Object parsedValue);
+ Object evaluate(String parsedValue);
}
}
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParser.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParser.java
new file mode 100644
index 000000000..54b18efb6
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParser.java
@@ -0,0 +1,11 @@
+package com.opensymphony.xwork2.util;
+
+/**
+ * Used to parse expressions like ${foo.bar} or %{bar.foo} but it is up tp the TextParser's
+ * implementation what kind of opening char to use (#, $, %, etc)
+ */
+public interface TextParser {
+
+ Object evaluate(char[] openChars, String expression, TextParseUtil.ParsedValueEvaluator evaluator, int maxLoopCount);
+
+}
diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java
index d1d15b222..3e31f9f5b 100644
--- a/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java
+++ b/xwork-core/src/test/java/com/opensymphony/xwork2/util/TextParseUtilTest.java
@@ -40,7 +40,7 @@ public class TextParseUtilTest extends XWorkTestCase {
});
TextParseUtil.ParsedValueEvaluator evaluator = new TextParseUtil.ParsedValueEvaluator() {
- public Object evaluate(Object parsedValue) {
+ public Object evaluate(String parsedValue) {
return parsedValue.toString()+"Something";
}
};
@@ -53,7 +53,7 @@ public class TextParseUtilTest extends XWorkTestCase {
public void testTranslateVariables() {
ValueStack stack = ActionContext.getContext().getValueStack();
- Object s = TextParseUtil.translateVariables("foo: ${{1, 2, 3}}, bar: ${1}", stack);
+ Object s = TextParseUtil.translateVariables("foo: ${{1, 2, 3}}, bar: %{1}", stack);
assertEquals("foo: [1, 2, 3], bar: 1", s);
s = TextParseUtil.translateVariables("foo: %{{1, 2, 3}}, bar: %{1}", stack);