el-based message resolution; expected failure right now

This commit is contained in:
Keith Donald 2009-06-13 17:18:12 +00:00
parent 97e7dfb398
commit d0079c6058
9 changed files with 284 additions and 154 deletions

View File

@ -16,10 +16,22 @@
package org.springframework.ui.message; package org.springframework.ui.message;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable; import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.support.StandardEvaluationContext;
class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable { class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable {
@ -27,22 +39,41 @@ class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable
private String[] codes; private String[] codes;
private Object[] args; private Map<String, Object> args;
private String defaultText; private String defaultText;
public DefaultMessageResolver(Severity severity, String[] codes, Object[] args, String defaultText) { private ExpressionParser expressionParser;
public DefaultMessageResolver(Severity severity, String[] codes, Map<String, Object> args,
String defaultText, ExpressionParser expressionParser) {
this.severity = severity; this.severity = severity;
this.codes = codes; this.codes = codes;
this.args = args; this.args = args;
this.defaultText = defaultText; this.defaultText = defaultText;
this.expressionParser = expressionParser;
} }
// implementing MessageResolver // implementing MessageResolver
public Message resolveMessage(MessageSource messageSource, Locale locale) { public Message resolveMessage(MessageSource messageSource, Locale locale) {
String text = messageSource.getMessage(this, locale); String messageString = messageSource.getMessage(this, locale);
Expression message;
try {
message = expressionParser.parseExpression(messageString, ParserContext.TEMPLATE_EXPRESSION);
} catch (ParseException e) {
throw new MessageResolutionException("Failed to parse message expression", e);
}
try {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(args);
context.addPropertyAccessor(new MapAccessor());
context.addPropertyAccessor(new MessageSourceResolvableAccessor(messageSource, locale));
String text = (String) message.getValue(context);
return new TextMessage(severity, text); return new TextMessage(severity, text);
} catch (EvaluationException e) {
throw new MessageResolutionException("Failed to evaluate expression to generate message text", e);
}
} }
// implementing MessageSourceResolver // implementing MessageSourceResolver
@ -52,7 +83,7 @@ class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable
} }
public Object[] getArguments() { public Object[] getArguments() {
return args; return null;
} }
public String getDefaultMessage() { public String getDefaultMessage() {
@ -60,7 +91,8 @@ class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable
} }
public String toString() { public String toString() {
return new ToStringCreator(this).append("severity", severity).append("codes", codes).append("args", args).append("defaultText", defaultText).toString(); return new ToStringCreator(this).append("severity", severity).append("codes", codes).append("defaultText",
defaultText).toString();
} }
static class TextMessage implements Message { static class TextMessage implements Message {
@ -84,4 +116,38 @@ class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable
} }
static class MessageSourceResolvableAccessor implements PropertyAccessor {
private MessageSource messageSource;
private Locale locale;
public MessageSourceResolvableAccessor(MessageSource messageSource, Locale locale) {
this.messageSource = messageSource;
this.locale = locale;
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
return true;
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
// TODO this does not get called when resolving MessageSourceResolvable variables; only when accessing properties on MessageSourceResolvable targets.
return new TypedValue(messageSource.getMessage((MessageSourceResolvable)target, locale));
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return false;
}
@SuppressWarnings("unchecked")
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
throw new UnsupportedOperationException("Should not be called");
}
public Class[] getSpecificTargetClasses() {
return new Class[] { MessageSourceResolvable.class };
}
}
} }

View File

@ -15,14 +15,18 @@
*/ */
package org.springframework.ui.message; package org.springframework.ui.message;
import java.util.ArrayList; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable; import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/** /**
* A convenient builder for building {@link MessageResolver} objects programmatically. * A convenient builder for building {@link MessageResolver} objects programmatically.
@ -46,16 +50,18 @@ import org.springframework.core.style.ToStringCreator;
*/ */
public class MessageBuilder { public class MessageBuilder {
private Set<String> codes = new LinkedHashSet<String>();
private Severity severity; private Severity severity;
private List<Object> args = new ArrayList<Object>(); private Set<String> codes = new LinkedHashSet<String>();
private Map<String, Object> args = new LinkedHashMap<String, Object>();
private String defaultText; private String defaultText;
private ExpressionParser expressionParser = new SpelExpressionParser();
/** /**
* Records the severity of the message. * Set the severity of the message.
* @return this, for fluent API usage * @return this, for fluent API usage
*/ */
public MessageBuilder severity(Severity severity) { public MessageBuilder severity(Severity severity) {
@ -64,8 +70,8 @@ public class MessageBuilder {
} }
/** /**
* Records that the message being built should try and resolve its text using the code provided. * Add a message code to use to resolve the message text.
* Adds the code to the codes list. Successive calls to this method add additional codes. * Successive calls to this method add additional codes.
* Codes are applied in the order they are added. * Codes are applied in the order they are added.
* @param code the message code * @param code the message code
* @return this, for fluent API usage * @return this, for fluent API usage
@ -76,31 +82,31 @@ public class MessageBuilder {
} }
/** /**
* Records that the message being built has a variable argument. * Add a message argument.
* Adds the arg to the args list. Successive calls to this method add additional args. * Successive calls to this method add additional args.
* Args are applied in the order they are added. * @param name the argument name
* @param arg the message argument value * @param value the argument value
* @return this, for fluent API usage * @return this, for fluent API usage
*/ */
public MessageBuilder arg(Object arg) { public MessageBuilder arg(String name, Object value) {
args.add(arg); args.put(name, value);
return this; return this;
} }
/** /**
* Records that the message being built has a variable argument, whose display value is also {@link MessageSourceResolvable}. * Add a message argument whose value is a resolvable message code.
* Adds the arg to the args list. Successive calls to this method add additional resolvable args. * Successive calls to this method add additional resolvable arguements.
* Args are applied in the order they are added. * @param name the argument name
* @param arg the resolvable message argument * @param value the argument value
* @return this, for fluent API usage * @return this, for fluent API usage
*/ */
public MessageBuilder resolvableArg(Object arg) { public MessageBuilder resolvableArg(String name, Object value) {
args.add(new ResolvableArgument(arg)); args.put(name, new ResolvableArgumentValue(value));
return this; return this;
} }
/** /**
* Records the fallback text of the message being built. * Set the fallback text for the message.
* If the message has no codes, this will always be used as the text. * If the message has no codes, this will always be used as the text.
* If the message has codes but none can be resolved, this will always be used as the text. * If the message has codes but none can be resolved, this will always be used as the text.
* @param text the default text * @param text the default text
@ -113,28 +119,28 @@ public class MessageBuilder {
/** /**
* Builds the message that will be resolved. * Builds the message that will be resolved.
* Call after recording builder instructions. * Call after recording all builder instructions.
* @return the built message resolver * @return the built message resolver
* @throws Illegal
*/ */
public MessageResolver build() { public MessageResolver build() {
if (severity == null) { if (severity == null) {
severity = Severity.INFO; severity = Severity.INFO;
} }
if (codes == null && defaultText == null) { if (codes == null && defaultText == null) {
throw new IllegalArgumentException( throw new IllegalStateException(
"A message code or the message text is required to build this message resolver"); "A message code or the message text is required to build this message resolver");
} }
String[] codesArray = (String[]) codes.toArray(new String[codes.size()]); String[] codesArray = (String[]) codes.toArray(new String[codes.size()]);
Object[] argsArray = args.toArray(new Object[args.size()]); return new DefaultMessageResolver(severity, codesArray, args, defaultText, expressionParser);
return new DefaultMessageResolver(severity, codesArray, argsArray, defaultText);
} }
private static class ResolvableArgument implements MessageSourceResolvable { static class ResolvableArgumentValue implements MessageSourceResolvable {
private Object arg; private Object value;
public ResolvableArgument(Object arg) { public ResolvableArgumentValue(Object value) {
this.arg = arg; this.value = value;
} }
public Object[] getArguments() { public Object[] getArguments() {
@ -142,15 +148,15 @@ public class MessageBuilder {
} }
public String[] getCodes() { public String[] getCodes() {
return new String[] { arg.toString() }; return new String[] { value.toString() };
} }
public String getDefaultMessage() { public String getDefaultMessage() {
return arg.toString(); return String.valueOf(value);
} }
public String toString() { public String toString() {
return new ToStringCreator(this).append("arg", arg).toString(); return new ToStringCreator(this).append("value", value).toString();
} }
} }

View File

@ -0,0 +1,9 @@
package org.springframework.ui.message;
public class MessageResolutionException extends RuntimeException {
public MessageResolutionException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -5,17 +5,17 @@ import static org.junit.Assert.assertEquals;
import java.util.Locale; import java.util.Locale;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.support.StaticMessageSource;
public class MessageBuilderTests { public class MessageBuilderTests {
private MessageBuilder builder = new MessageBuilder(); private MessageBuilder builder = new MessageBuilder();
@Test @Test
public void buildMessage() { public void buildMessage() {
MessageResolver resolver = builder.severity(Severity.ERROR).code("invalidFormat").resolvableArg("mathForm.decimalField") MessageResolver resolver = builder.severity(Severity.ERROR).code("invalidFormat").resolvableArg("label", "mathForm.decimalField")
.arg("#,###.##").defaultText("Field must be in format #,###.##").build(); .arg("format", "#,###.##").defaultText("Field must be in format #,###.##").build();
StaticMessageSource messageSource = new StaticMessageSource(); MockMessageSource messageSource = new MockMessageSource();
messageSource.addMessage("invalidFormat", Locale.US, "{0} must be in format {1}"); messageSource.addMessage("invalidFormat", Locale.US, "#{label} must be in format #{format}");
messageSource.addMessage("mathForm.decimalField", Locale.US, "Decimal Field"); messageSource.addMessage("mathForm.decimalField", Locale.US, "Decimal Field");
Message message = resolver.resolveMessage(messageSource, Locale.US); Message message = resolver.resolveMessage(messageSource, Locale.US);
assertEquals(Severity.ERROR, message.getSeverity()); assertEquals(Severity.ERROR, message.getSeverity());

View File

@ -0,0 +1,48 @@
package org.springframework.ui.message;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.util.Assert;
public class MockMessageSource extends AbstractMessageSource {
/** Map from 'code + locale' keys to message Strings */
private final Map<String, String> messages = new HashMap<String, String>();
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
throw new IllegalStateException("Should not be called");
}
@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
return this.messages.get(code + "_" + locale.toString());
}
/**
* Associate the given message with the given code.
* @param code the lookup code
* @param locale the locale that the message should be found within
* @param msg the message associated with this lookup code
*/
public void addMessage(String code, Locale locale, String msg) {
Assert.notNull(code, "Code must not be null");
Assert.notNull(locale, "Locale must not be null");
Assert.notNull(msg, "Message must not be null");
this.messages.put(code + "_" + locale.toString(), msg);
if (logger.isDebugEnabled()) {
logger.debug("Added message [" + msg + "] for code [" + code + "] and Locale [" + locale + "]");
}
}
@Override
public String toString() {
return getClass().getName() + ": " + this.messages;
}
}

View File

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.expression; package org.springframework.expression;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
@ -69,13 +68,13 @@ public interface Expression {
public <T> T getValue(EvaluationContext context, Class<T> desiredResultType) throws EvaluationException; public <T> T getValue(EvaluationContext context, Class<T> desiredResultType) throws EvaluationException;
/** /**
* Set this expression in the provided context to the value provided. * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
* the default context.
* *
* @param context the context in which to set the value of the expression * @return the most general type of value that can be set on this context
* @param value the new value * @throws EvaluationException if there is a problem determining the type
* @throws EvaluationException if there is a problem during evaluation
*/ */
public void setValue(EvaluationContext context, Object value) throws EvaluationException; public Class getValueType() throws EvaluationException;
/** /**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
@ -91,10 +90,10 @@ public interface Expression {
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using
* the default context. * the default context.
* *
* @return the most general type of value that can be set on this context * @return a type descriptor for the most general type of value that can be set on this context
* @throws EvaluationException if there is a problem determining the type * @throws EvaluationException if there is a problem determining the type
*/ */
public Class getValueType() throws EvaluationException; public TypeDescriptor getValueTypeDescriptor() throws EvaluationException;
/** /**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for * Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method for
@ -107,14 +106,22 @@ public interface Expression {
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException; public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException;
/** /**
* Returns the most general type that can be passed to the {@link #setValue(EvaluationContext, Object)} method using * Determine if an expression can be written to, i.e. setValue() can be called.
* the default context.
* *
* @return a type descriptor for the most general type of value that can be set on this context * @param context the context in which the expression should be checked
* @throws EvaluationException if there is a problem determining the type * @return true if the expression is writable
* @throws EvaluationException if there is a problem determining if it is writable
*/ */
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException; public boolean isWritable(EvaluationContext context) throws EvaluationException;
/**
* Set this expression in the provided context to the value provided.
*
* @param context the context in which to set the value of the expression
* @param value the new value
* @throws EvaluationException if there is a problem during evaluation
*/
public void setValue(EvaluationContext context, Object value) throws EvaluationException;
/** /**
* Returns the original string used to create this expression, unmodified. * Returns the original string used to create this expression, unmodified.
@ -123,12 +130,4 @@ public interface Expression {
*/ */
public String getExpressionString(); public String getExpressionString();
/**
* Determine if an expression can be written to, i.e. setValue() can be called.
*
* @param context the context in which the expression should be checked
* @return true if the expression is writable
* @throws EvaluationException if there is a problem determining if it is writable
*/
public boolean isWritable(EvaluationContext context) throws EvaluationException;
} }

View File

@ -31,8 +31,8 @@ public interface ParserContext {
* *
* <pre> * <pre>
* Some literal text * Some literal text
* Hello ${name.firstName}! * Hello #{name.firstName}!
* ${3 + 4} * #{3 + 4}
* </pre> * </pre>
* *
* @return true if the expression is a template, false otherwise * @return true if the expression is a template, false otherwise
@ -55,4 +55,25 @@ public interface ParserContext {
*/ */
String getExpressionSuffix(); String getExpressionSuffix();
/**
* The default ParserContext implementation that enables template expression parsing mode.
* The expression prefix is #{ and the expression suffix is }.
* @see #isTemplate()
*/
public static final ParserContext TEMPLATE_EXPRESSION = new ParserContext() {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
};
} }

View File

@ -31,6 +31,12 @@ public class TemplateParserContext implements ParserContext {
private final String expressionSuffix; private final String expressionSuffix;
/**
* Creates a new TemplateParserContext with the default #{ prefix and } suffix.
*/
public TemplateParserContext() {
this("#{", "}");
}
/** /**
* Create a new TemplateParserContext for the given prefix and suffix. * Create a new TemplateParserContext for the given prefix and suffix.

View File

@ -22,6 +22,7 @@ import org.springframework.expression.Expression;
import org.springframework.expression.common.ExpressionUtils; import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ast.SpelNodeImpl; import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
/** /**
* A SpelExpressions represents a parsed (valid) expression that is ready to be evaluated in a specified context. An * A SpelExpressions represents a parsed (valid) expression that is ready to be evaluated in a specified context. An
@ -48,35 +49,27 @@ public class SpelExpression implements Expression {
this.configuration = configuration; this.configuration = configuration;
} }
/** // implementing Expression
* @return the expression string that was parsed to create this expression instance
*/
public String getExpressionString() {
return this.expression;
}
/**
* {@inheritDoc}
*/
public Object getValue() throws EvaluationException { public Object getValue() throws EvaluationException {
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(),configuration); ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(), configuration);
return this.ast.getValue(expressionState); return ast.getValue(expressionState);
}
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(), configuration);
Object result = ast.getValue(expressionState);
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
} }
/**
* {@inheritDoc}
*/
public Object getValue(EvaluationContext context) throws EvaluationException { public Object getValue(EvaluationContext context) throws EvaluationException {
return this.ast.getValue(new ExpressionState(context,configuration)); Assert.notNull(context, "The EvaluationContext is required");
return ast.getValue(new ExpressionState(context, configuration));
} }
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException { public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
Object result = ast.getValue(new ExpressionState(context,configuration)); Object result = ast.getValue(new ExpressionState(context, configuration));
if (result != null && expectedResultType != null) { if (result != null && expectedResultType != null) {
Class<?> resultType = result.getClass(); Class<?> resultType = result.getClass();
if (!expectedResultType.isAssignableFrom(resultType)) { if (!expectedResultType.isAssignableFrom(resultType)) {
@ -87,25 +80,49 @@ public class SpelExpression implements Expression {
return (T) result; return (T) result;
} }
/** public Class getValueType() throws EvaluationException {
* {@inheritDoc} return ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(), configuration)).getTypeDescriptor().getType();
*/
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
this.ast.setValue(new ExpressionState(context,configuration), value);
} }
/** public Class getValueType(EvaluationContext context) throws EvaluationException {
* {@inheritDoc} Assert.notNull(context, "The EvaluationContext is required");
*/ ExpressionState eState = new ExpressionState(context, configuration);
public boolean isWritable(EvaluationContext context) throws EvaluationException { TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
return this.ast.isWritable(new ExpressionState(context,configuration)); return typeDescriptor.getType();
} }
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
return ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(), configuration)).getTypeDescriptor();
}
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ExpressionState eState = new ExpressionState(context, configuration);
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor;
}
public String getExpressionString() {
return expression;
}
public boolean isWritable(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return ast.isWritable(new ExpressionState(context, configuration));
}
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
ast.setValue(new ExpressionState(context, configuration), value);
}
// impl only
/** /**
* @return return the Abstract Syntax Tree for the expression * @return return the Abstract Syntax Tree for the expression
*/ */
public SpelNode getAST() { public SpelNode getAST() {
return this.ast; return ast;
} }
/** /**
@ -116,49 +133,7 @@ public class SpelExpression implements Expression {
* @return the string representation of the AST * @return the string representation of the AST
*/ */
public String toStringAST() { public String toStringAST() {
return this.ast.toStringAST(); return ast.toStringAST();
}
/**
* {@inheritDoc}
*/
public Class getValueType(EvaluationContext context) throws EvaluationException {
ExpressionState eState = new ExpressionState(context,configuration);
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor.getType();
}
/**
* {@inheritDoc}
*/
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
ExpressionState eState = new ExpressionState(context,configuration);
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
return typeDescriptor;
}
/**
* {@inheritDoc}
*/
public Class getValueType() throws EvaluationException {
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(),configuration)).getTypeDescriptor().getType();
}
/**
* {@inheritDoc}
*/
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(),configuration)).getTypeDescriptor();
}
/**
* {@inheritDoc}
*/
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(),configuration);
Object result = this.ast.getValue(expressionState);
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
} }
} }