separated out the concept of a template
normalized EL and OGNL to respect this concept
This commit is contained in:
@@ -16,16 +16,23 @@
|
||||
package org.springframework.binding.expression;
|
||||
|
||||
/**
|
||||
* Parses expression strings, returning a configured evaluator instance capable of performing parsed expression
|
||||
* evaluation in a thread safe way.
|
||||
* Parses expression strings into compiled expressions that can be evaluated. Supports parsing templates as well as
|
||||
* standard expression strings.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public interface ExpressionParser {
|
||||
|
||||
/**
|
||||
* Parse the provided expression string, returning an expression evaluator capable of evaluating it.
|
||||
* @param expressionString the parseable expression string; cannot be null (required)
|
||||
* Parse the expression string and return a compiled Expression object you can use for evaluation. Some examples:
|
||||
*
|
||||
* <pre>
|
||||
* 3 + 4
|
||||
* name.firstName
|
||||
* </pre>
|
||||
*
|
||||
* @param expressionString the raw expression spring to parse; cannot be null; cannot be encased in any special
|
||||
* delimiters; cannot be a template
|
||||
* @param context a context used to set attributes that influence expression parsing routine (optional)
|
||||
* @return the evaluator for the parsed expression
|
||||
* @throws ParserException an exception occurred during parsing
|
||||
|
||||
@@ -9,7 +9,9 @@ import org.springframework.util.Assert;
|
||||
public class ExpressionVariable {
|
||||
|
||||
private String name;
|
||||
|
||||
private String valueExpression;
|
||||
|
||||
private ParserContext parserContext;
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,4 +25,19 @@ public interface ParserContext {
|
||||
* expression parser will register these variables for reference during evaluation.
|
||||
*/
|
||||
public ExpressionVariable[] getExpressionVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the expression being parsed is a template. A template expression consists of literal text that can
|
||||
* be mixed with evaluatable blocks. Some examples:
|
||||
*
|
||||
* <pre>
|
||||
* Some literal text
|
||||
* Hello #{name.firstName}!
|
||||
* #{3 + 4}
|
||||
* </pre>
|
||||
*
|
||||
* @return true if the expression is a template, false otherwise
|
||||
*/
|
||||
public boolean isTemplate();
|
||||
|
||||
}
|
||||
@@ -55,6 +55,17 @@ public class ELExpressionParser implements ExpressionParser {
|
||||
if (context == null) {
|
||||
context = NullParserContext.INSTANCE;
|
||||
}
|
||||
if (context.isTemplate()) {
|
||||
return parseTemplate(expressionString, context);
|
||||
} else {
|
||||
assertNotDelimited(expressionString);
|
||||
assertHasText(expressionString);
|
||||
return parseTemplate("#{" + expressionString + "}", context);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression parseTemplate(String expressionString, ParserContext context) throws ParserException {
|
||||
Assert.notNull(expressionString, "The expression string to parse is required");
|
||||
try {
|
||||
ValueExpression expression = parseValueExpression(expressionString, context);
|
||||
ELContextFactory contextFactory = getContextFactory(context.getEvaluationContextType(), expressionString);
|
||||
@@ -94,6 +105,24 @@ public class ELExpressionParser implements ExpressionParser {
|
||||
putContextFactory(Object.class, defaultContextFactory);
|
||||
}
|
||||
|
||||
private void assertNotDelimited(String expressionString) {
|
||||
if ((expressionString.startsWith("#{") && expressionString.endsWith("}"))
|
||||
|| (expressionString.startsWith("${") && expressionString.endsWith("}"))) {
|
||||
throw new ParserException(
|
||||
expressionString,
|
||||
"This expression '"
|
||||
+ expressionString
|
||||
+ "' being parsed is expected be an 'eval' EL expression string. Do not attempt to enclose such expression strings in #{} or ${} delimiters--this is redundant. If you need to parse a template that mixes literal text with evaluatable blocks, set the 'template' parser context attribute to true.",
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertHasText(String expressionString) {
|
||||
if (expressionString.length() == 0) {
|
||||
throw new ParserException(expressionString, "The EL eval expression to parse must have text", null);
|
||||
}
|
||||
}
|
||||
|
||||
private class ParserELContext extends ELContext {
|
||||
private VariableMapper variableMapper;
|
||||
|
||||
@@ -116,7 +145,14 @@ public class ELExpressionParser implements ExpressionParser {
|
||||
ExpressionVariable var = variables[i];
|
||||
ParserContext context = var.getParserContext() != null ? var.getParserContext()
|
||||
: NullParserContext.INSTANCE;
|
||||
ValueExpression expr = parseValueExpression(var.getValueExpression(), context);
|
||||
ValueExpression expr;
|
||||
if (context.isTemplate()) {
|
||||
expr = parseValueExpression(var.getValueExpression(), context);
|
||||
} else {
|
||||
assertNotDelimited(var.getValueExpression());
|
||||
assertHasText(var.getValueExpression());
|
||||
expr = parseValueExpression("#{" + var.getValueExpression() + "}", context);
|
||||
}
|
||||
variableMapper.setVariable(var.getName(), expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +63,11 @@ public class OgnlExpressionParser implements ExpressionParser {
|
||||
private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX;
|
||||
|
||||
/**
|
||||
* Should we allow undelimited OGNL eval expressions like "foo.bar"? If not, evalutable OGNL expressions must be
|
||||
* enclosed in delimiters like ${foo.bar} else they are treated as literal expressions. Mainly here for
|
||||
* compatability reasons, as Web Flow 1.0 allows undelimited OGNL eval expressions by default.
|
||||
* Should we allow delimited OGNL eval expressions like "#{foo.bar}"? If not, evalutable OGNL expressions must not
|
||||
* be enclosed in delimiters like ${foo.bar} else an exception is thrown. Only here for compatability reasons, as
|
||||
* Web Flow 1.0 allows delimited OGNL eval expressions while 2.x does not.
|
||||
*/
|
||||
private boolean allowUndelimitedEvalExpressions;
|
||||
private boolean allowDelimitedEvalExpressions;
|
||||
|
||||
/**
|
||||
* Returns the configured expression delimiter prefix. Defaults to "${".
|
||||
@@ -98,26 +98,17 @@ public class OgnlExpressionParser implements ExpressionParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this parser should we allow undelimited OGNL eval expressions like <code>foo.bar</code>.
|
||||
* Returns if this parser allows delimited OGNL eval expressions like <code>${foo.bar}</code>.
|
||||
*/
|
||||
public boolean getAllowUndelimitedEvalExpressions() {
|
||||
return allowUndelimitedEvalExpressions;
|
||||
public boolean getAllowDelimitedEvalExpressions() {
|
||||
return allowDelimitedEvalExpressions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if this parser should allow undelimited OGNL eval expressions like "foo.bar"? If not, evalutable OGNL
|
||||
* expressions must be enclosed in delimiters like ${foo.bar}, else they are treated as literal expressions.
|
||||
* Sets if this parser allows OGNL eval expressions like ${foo.bar}.
|
||||
*/
|
||||
public void setAllowUndelimitedEvalExpressions(boolean allowUndelmitedEvalExpressions) {
|
||||
this.allowUndelimitedEvalExpressions = allowUndelmitedEvalExpressions;
|
||||
}
|
||||
|
||||
public static String getDEFAULT_EXPRESSION_PREFIX() {
|
||||
return DEFAULT_EXPRESSION_PREFIX;
|
||||
}
|
||||
|
||||
public static String getDEFAULT_EXPRESSION_SUFFIX() {
|
||||
return DEFAULT_EXPRESSION_SUFFIX;
|
||||
public void setAllowDelimitedEvalExpressions(boolean allowDelmitedEvalExpressions) {
|
||||
this.allowDelimitedEvalExpressions = allowDelmitedEvalExpressions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,6 +123,33 @@ public class OgnlExpressionParser implements ExpressionParser {
|
||||
// expression parser
|
||||
|
||||
public Expression parseExpression(String expressionString, ParserContext context) throws ParserException {
|
||||
Assert.notNull(expressionString, "The expression string to parse is required");
|
||||
if (context == null) {
|
||||
context = NullParserContext.INSTANCE;
|
||||
}
|
||||
if (context.isTemplate()) {
|
||||
return parseTemplate(expressionString, context);
|
||||
} else {
|
||||
if (expressionString.startsWith(getExpressionPrefix()) && expressionString.endsWith(getExpressionSuffix())) {
|
||||
if (!allowDelimitedEvalExpressions) {
|
||||
throw new ParserException(
|
||||
expressionString,
|
||||
"The expression '"
|
||||
+ expressionString
|
||||
+ "' being parsed is expected be a standard OGNL expression. Do not attempt to enclose such expression strings in ${} delimiters--this is redundant. If you need to parse a template that mixes literal text with evaluatable blocks, set the 'template' parser context attribute to true.",
|
||||
null);
|
||||
} else {
|
||||
int lastIndex = expressionString.length() - getExpressionSuffix().length();
|
||||
String ognlExpression = expressionString.substring(getExpressionPrefix().length(), lastIndex);
|
||||
return doParseExpression(ognlExpression, context);
|
||||
}
|
||||
} else {
|
||||
return doParseExpression(expressionString, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Expression parseTemplate(String expressionString, ParserContext context) throws ParserException {
|
||||
Assert.notNull(expressionString, "The expression string to parse is required");
|
||||
if (expressionString.length() == 0) {
|
||||
return parseEmptyExpressionString(context);
|
||||
@@ -144,62 +162,13 @@ public class OgnlExpressionParser implements ExpressionParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided string a template expression this parser can parse? Always returns <code>true</code> if this
|
||||
* OGNL expression parser is configured to <b>not</b> allow undelimited OGNL expressions. If undelimited OGNL
|
||||
* expressions are allowed like "foo.bar", this method only returns true if an explicitly delimited expression is
|
||||
* present in the string like "hello my name is ${name}" or "${foo.bar}".
|
||||
*
|
||||
* In general, a template expression is either:
|
||||
* <ol>
|
||||
* <li>static literal text like "hello world". In this case, evaluating the expression simply returns the literal
|
||||
* text.
|
||||
* <li>a single eval expression like ${requestParameters.foo}. In this case, evaluating the expression returns the
|
||||
* evaluated value.
|
||||
* <li>a mix of literal text with one or more eval expressions like "hello #{name}". In this case, evaluating the
|
||||
* expression returns a string with the result of #{name} evaluated (often called a composite expression).
|
||||
* </ol>
|
||||
*
|
||||
* This method and the {@link #getAllowUndelimitedEvalExpressions()} flag primarily exist for compatibility reasons.
|
||||
* The OgnlExpressionParser in SWF 1.0 does not treat literal text like "hello world" as a template expression, but
|
||||
* rather a standard, evaluatable OGNL expression. Therefore, callers expecting standard template evaluation
|
||||
* semantics are expected to work with these literal string values themselves, and not pass those strings to
|
||||
* {@link #parseExpression(String, ParserContext)}.
|
||||
*
|
||||
* @param string the string
|
||||
* @return true if the string is a template expression, false otherwise.
|
||||
*/
|
||||
public boolean isTemplateExpression(String string) {
|
||||
if (!allowUndelimitedEvalExpressions) {
|
||||
// every string provided is a "template" style expression - return true
|
||||
return true;
|
||||
}
|
||||
// only returns true when there is ${} somewhere in the string
|
||||
// this is version 1.0 semantics, there for compatability reasons
|
||||
int prefixIndex = string.indexOf(getExpressionPrefix());
|
||||
if (prefixIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
int suffixIndex = string.indexOf(getExpressionSuffix(), prefixIndex);
|
||||
if (suffixIndex == -1) {
|
||||
return false;
|
||||
} else {
|
||||
// make sure there is actually something inside the ${}
|
||||
if (suffixIndex == prefixIndex + getExpressionPrefix().length()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
||||
/**
|
||||
* Helper that handles a empty expression string.
|
||||
*/
|
||||
private Expression parseEmptyExpressionString(ParserContext context) {
|
||||
if (allowUndelimitedEvalExpressions) {
|
||||
if (allowDelimitedEvalExpressions) {
|
||||
// let the parser handle it
|
||||
return doParseExpression("", context);
|
||||
} else {
|
||||
@@ -253,7 +222,7 @@ public class OgnlExpressionParser implements ExpressionParser {
|
||||
} else {
|
||||
if (startIdx == 0) {
|
||||
// treat the entire string as one expression
|
||||
if (allowUndelimitedEvalExpressions) {
|
||||
if (allowDelimitedEvalExpressions) {
|
||||
expressions.add(doParseExpression(expressionString, context));
|
||||
} else {
|
||||
// treat entire string as a literal
|
||||
|
||||
@@ -44,4 +44,8 @@ public final class NullParserContext implements ParserContext {
|
||||
public ExpressionVariable[] getExpressionVariables() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isTemplate() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import org.springframework.binding.expression.ParserContext;
|
||||
|
||||
/**
|
||||
* Default implementation of the ParserContext interface that has a fluent API for building parser context attributes.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class ParserContextImpl implements ParserContext {
|
||||
@@ -20,12 +19,15 @@ public class ParserContextImpl implements ParserContext {
|
||||
|
||||
private List expressionVariables;
|
||||
|
||||
private boolean template;
|
||||
|
||||
/**
|
||||
* Create a new parser context, initially with all context attributes as null. Post construction, call one or more
|
||||
* of the fluent builder methods to configure this context.
|
||||
* @see #eval(Class)
|
||||
* @see #expect(Class)
|
||||
* @see #variable(ExpressionVariable)
|
||||
* @see #template()
|
||||
*/
|
||||
public ParserContextImpl() {
|
||||
init();
|
||||
@@ -43,6 +45,10 @@ public class ParserContextImpl implements ParserContext {
|
||||
return (ExpressionVariable[]) expressionVariables.toArray(new ExpressionVariable[expressionVariables.size()]);
|
||||
}
|
||||
|
||||
public boolean isTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the evaluationContextType attribute with the value provided.
|
||||
* @param contextType the type of context object the parsed expression will evaluate in
|
||||
@@ -50,7 +56,7 @@ public class ParserContextImpl implements ParserContext {
|
||||
*/
|
||||
public ParserContextImpl eval(Class contextType) {
|
||||
evaluationContextType = contextType;
|
||||
return ParserContextImpl.this;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +66,7 @@ public class ParserContextImpl implements ParserContext {
|
||||
*/
|
||||
public ParserContextImpl expect(Class resultType) {
|
||||
evaluationResultType = resultType;
|
||||
return ParserContextImpl.this;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +76,7 @@ public class ParserContextImpl implements ParserContext {
|
||||
*/
|
||||
public ParserContextImpl variable(ExpressionVariable variable) {
|
||||
expressionVariables.add(variable);
|
||||
return ParserContextImpl.this;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +86,16 @@ public class ParserContextImpl implements ParserContext {
|
||||
*/
|
||||
public ParserContextImpl variables(ExpressionVariable[] variables) {
|
||||
expressionVariables.addAll(Arrays.asList(variables));
|
||||
return ParserContextImpl.this;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag indicating the expression to parse is a template.
|
||||
* @return this
|
||||
*/
|
||||
public ParserContextImpl template() {
|
||||
template = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
||||
Reference in New Issue
Block a user