From 34af1cd8a212b366565c4d1888dc5f2504efd000 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Fri, 15 Mar 2013 14:02:12 +0000 Subject: [PATCH] WW-3998 Extracts interface and adds default implementation base on existing code git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@1456961 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/struts2/StrutsConstants.java | 2 + .../struts2/config/BeanSelectionProvider.java | 14 ++- .../dispatcher/StrutsResultSupport.java | 4 +- core/src/main/resources/struts-default.xml | 2 + .../apache/struts2/EmbeddedJSPResultTest.java | 4 + .../views/java/simple/AbstractTest.java | 6 +- .../providers/XWorkConfigurationProvider.java | 7 +- .../interceptor/ParametersInterceptor.java | 2 +- .../xwork2/util/OgnlTextParser.java | 84 +++++++++++++++++ .../xwork2/util/TextParseUtil.java | 90 ++++--------------- .../opensymphony/xwork2/util/TextParser.java | 11 +++ .../xwork2/util/TextParseUtilTest.java | 4 +- 12 files changed, 141 insertions(+), 89 deletions(-) create mode 100644 xwork-core/src/main/java/com/opensymphony/xwork2/util/OgnlTextParser.java create mode 100644 xwork-core/src/main/java/com/opensymphony/xwork2/util/TextParser.java 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);