add support for ConversionService use with EL parser; fully implement Errors

This commit is contained in:
Keith Donald
2008-08-10 04:32:52 +00:00
parent 8fed3f02c0
commit 2f368897d6
8 changed files with 312 additions and 61 deletions

View File

@@ -0,0 +1,37 @@
package org.springframework.binding.convert.converters;
import org.springframework.util.NumberUtils;
/**
* A one-way converter that can convert from any JDK-standard Number implementation to any other JDK-standard Number
* implementation.
*
* Support Number classes include byte, short, integer, float, double, long, big integer, big decimal. This class
* delegates to {@link NumberUtils#convertNumberToTargetClass(Number, Class)} to perform the conversion.
*
* @see java.lang.Byte
* @see java.lang.Short
* @see java.lang.Integer
* @see java.lang.Long
* @see java.math.BigInteger
* @see java.lang.Float
* @see java.lang.Double
* @see java.math.BigDecimal
*
* @author Keith Donald
*/
public class NumberToNumber implements Converter {
public Class getSourceClass() {
return Number.class;
}
public Class getTargetClass() {
return Number.class;
}
public Object convertSourceToTargetClass(Object source, Class targetClass) throws Exception {
return NumberUtils.convertNumberToTargetClass((Number) source, targetClass);
}
}

View File

@@ -20,6 +20,7 @@ import java.math.BigInteger;
import java.util.Date;
import java.util.Locale;
import org.springframework.binding.convert.converters.NumberToNumber;
import org.springframework.binding.convert.converters.ObjectToCollection;
import org.springframework.binding.convert.converters.StringToBigDecimal;
import org.springframework.binding.convert.converters.StringToBigInteger;
@@ -70,6 +71,7 @@ public class DefaultConversionService extends GenericConversionService {
addConverter(new StringToDate());
addConverter(new StringToLabeledEnum());
addConverter(new ObjectToCollection(this));
addConverter(new NumberToNumber());
}
protected void addDefaultAliases() {

View File

@@ -0,0 +1,95 @@
package org.springframework.binding.expression.el;
import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ExpressionFactory;
import javax.el.PropertyNotFoundException;
import javax.el.PropertyNotWritableException;
import javax.el.ValueExpression;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.util.Assert;
/**
* A ValueExpression decorator that decorates a target ValueExpression returned by a EL Implementation's
* {@link ExpressionFactory} to allow use of Spring's type conversion system for coersing expression values.
*
* The Unified EL specification currently provides no standard way of plugging in custom type converters. This decorator
* allows Spring type converters to be utilized with any EL implementation.
*
* @author Keith Donald
*/
class BindingValueExpression extends ValueExpression {
private ValueExpression targetExpression;
private Class expectedType;
private ConversionService conversionService;
private boolean template;
public BindingValueExpression(ValueExpression targetExpression, Class expectedType,
ConversionService conversionService, boolean template) {
Assert.notNull(expectedType, "The expectedType Class is required");
Assert.notNull(conversionService, "The ConversionService to perform type coersions is required");
this.targetExpression = targetExpression;
this.expectedType = expectedType;
this.conversionService = conversionService;
this.template = template;
}
public Class getExpectedType() {
return targetExpression.getExpectedType();
}
public Class getType(ELContext context) throws NullPointerException, PropertyNotFoundException, ELException {
return targetExpression.getType(context);
}
public Object getValue(ELContext context) throws NullPointerException, PropertyNotFoundException, ELException {
Object value = targetExpression.getValue(context);
return conversionService.executeConversion(value, expectedType);
}
public boolean isReadOnly(ELContext context) throws NullPointerException, PropertyNotFoundException, ELException {
return targetExpression.isReadOnly(context);
}
public void setValue(ELContext context, Object value) throws NullPointerException, PropertyNotFoundException,
PropertyNotWritableException, ELException {
Class targetType = targetExpression.getType(context);
if (value != null && targetType != null) {
ConversionExecutor converter = conversionService.getConversionExecutor(value.getClass(), targetType);
value = converter.execute(value);
}
targetExpression.setValue(context, value);
}
public String getExpressionString() {
if (template) {
return targetExpression.getExpressionString();
} else {
String rawExpressionString = targetExpression.getExpressionString();
return rawExpressionString.substring("#{".length(), rawExpressionString.length() - 1);
}
}
public boolean isLiteralText() {
return targetExpression.isLiteralText();
}
public boolean equals(Object obj) {
if (!(obj instanceof BindingValueExpression)) {
return false;
}
BindingValueExpression exp = (BindingValueExpression) obj;
return targetExpression.equals(exp.targetExpression);
}
public int hashCode() {
return targetExpression.hashCode();
}
}

View File

@@ -19,8 +19,6 @@ import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ValueExpression;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.PropertyNotFoundException;
@@ -37,26 +35,16 @@ public class ELExpression implements Expression {
private ValueExpression valueExpression;
private boolean template;
private ConversionService conversionService;
/**
* Creates a new el expression
* @param factory the el context factory for creating the EL context that will be used during expression evaluation
* @param valueExpression the value expression to evaluate
* @param template whether or not this expression is a template expression; if not it was parsed as an implict eval
* expression (without delimiters)
*/
public ELExpression(ELContextFactory factory, ValueExpression valueExpression, ConversionService conversionService,
boolean template) {
public ELExpression(ELContextFactory factory, ValueExpression valueExpression) {
Assert.notNull(factory, "The ELContextFactory is required to evaluate EL expressions");
Assert.notNull(valueExpression, "The EL ValueExpression is required for evaluation");
Assert.notNull(conversionService, "The ConversionService to perform type coersions is required");
this.elContextFactory = factory;
this.valueExpression = valueExpression;
this.conversionService = conversionService;
this.template = template;
}
public Object getValue(Object context) throws EvaluationException {
@@ -86,11 +74,6 @@ public class ELExpression implements Expression {
public void setValue(Object context, Object value) throws EvaluationException {
ELContext ctx = elContextFactory.getELContext(context);
try {
Class targetType = getValueType(context);
if (value != null && targetType != null) {
ConversionExecutor converter = conversionService.getConversionExecutor(value.getClass(), targetType);
value = converter.execute(value);
}
valueExpression.setValue(ctx, value);
if (!ctx.isPropertyResolved()) {
throw new EvaluationException(context.getClass(), getExpressionString(), "The expression '"
@@ -120,12 +103,7 @@ public class ELExpression implements Expression {
}
public String getExpressionString() {
if (template) {
return valueExpression.getExpressionString();
} else {
String rawExpressionString = valueExpression.getExpressionString();
return rawExpressionString.substring("#{".length(), rawExpressionString.length() - 1);
}
return valueExpression.getExpressionString();
}
private String getBaseVariable() {

View File

@@ -103,7 +103,7 @@ public class ELExpressionParser implements ExpressionParser {
try {
ValueExpression expression = parseValueExpression(expressionString, context);
ELContextFactory contextFactory = getContextFactory(context.getEvaluationContextType(), expressionString);
return new ELExpression(contextFactory, expression, conversionService, template);
return new ELExpression(contextFactory, expression);
} catch (ELException e) {
throw new ParserException(expressionString, e);
}
@@ -112,16 +112,13 @@ public class ELExpressionParser implements ExpressionParser {
private ValueExpression parseValueExpression(String expressionString, ParserContext context) throws ELException {
ParserELContext elContext = new ParserELContext();
elContext.mapVariables(context.getExpressionVariables(), expressionFactory);
return expressionFactory.createValueExpression(elContext, expressionString, getExpectedType(context));
ValueExpression expression = expressionFactory.createValueExpression(elContext, expressionString, Object.class);
return new BindingValueExpression(expression, getExpectedType(context), conversionService, context.isTemplate());
}
private Class getExpectedType(ParserContext context) {
Class expectedType = context.getExpectedEvaluationResultType();
if (expectedType != null) {
return expectedType;
} else {
return Object.class;
}
return expectedType != null ? expectedType : Object.class;
}
private ELContextFactory getContextFactory(Class expressionTargetType, String expressionString) {
@@ -134,20 +131,19 @@ public class ELExpressionParser implements ExpressionParser {
private void init(ExpressionFactory expressionFactory) {
this.expressionFactory = expressionFactory;
DefaultElContextFactory defaultContextFactory = new DefaultElContextFactory();
putContextFactory(null, defaultContextFactory);
putContextFactory(Object.class, defaultContextFactory);
DefaultElContextFactory contextFactory = new DefaultElContextFactory();
putContextFactory(null, contextFactory);
putContextFactory(Object.class, contextFactory);
}
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);
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. "
+ "If you need to parse a template that mixes literal text with evaluatable blocks, "
+ "set the 'template' parser context attribute to true.", null);
}
}

View File

@@ -15,10 +15,21 @@
*/
package org.springframework.binding.message;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.support.FluentParserContext;
import org.springframework.binding.mapping.MappingResult;
import org.springframework.binding.mapping.MappingResults;
import org.springframework.binding.mapping.MappingResultsCriteria;
import org.springframework.validation.AbstractErrors;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
/**
* Adapts a MessageContext object to the Spring Errors interface. Allows Spring Validators to record errors that are
@@ -30,12 +41,27 @@ public class MessageContextErrors extends AbstractErrors {
private MessageContext messageContext;
private String objectName;
private Object boundObject;
private ExpressionParser expressionParser;
private MappingResults mappingResults;
/**
* Creates a new message context errors adapter.
* @param messageContext the backing message context
* @param objectName the object name
*/
public MessageContextErrors(MessageContext messageContext) {
public MessageContextErrors(MessageContext messageContext, String objectName, Object boundObject,
ExpressionParser expressionParser, MappingResults mappingResults) {
this.messageContext = messageContext;
this.messageContext = messageContext;
this.boundObject = boundObject;
this.objectName = objectName;
this.expressionParser = expressionParser;
this.mappingResults = mappingResults;
}
public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
@@ -49,23 +75,97 @@ public class MessageContextErrors extends AbstractErrors {
}
public void addAllErrors(Errors errors) {
throw new UnsupportedOperationException("Not expected to be called by a validator");
}
public List getFieldErrors() {
throw new UnsupportedOperationException("Not expected to be called by a validator");
}
public Object getFieldValue(String field) {
throw new UnsupportedOperationException("Not expected to be called by a validator");
}
public List getGlobalErrors() {
throw new UnsupportedOperationException("Not expected to be called by a validator");
Iterator it = errors.getAllErrors().iterator();
while (it.hasNext()) {
ObjectError error = (ObjectError) it.next();
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
rejectValue(fieldError.getField(), error.getCode(), error.getArguments(), error.getDefaultMessage());
} else {
reject(error.getCode(), error.getArguments(), error.getDefaultMessage());
}
}
}
public String getObjectName() {
throw new UnsupportedOperationException("Not expected to be called by a validator");
return objectName;
}
}
public List getGlobalErrors() {
Message[] messages = messageContext.getMessagesByCriteria(GLOBAL_ERROR);
if (messages.length == 0) {
return Collections.EMPTY_LIST;
}
List errors = new ArrayList(messages.length);
for (int i = 0; i < messages.length; i++) {
Message message = messages[i];
errors.add(new ObjectError(objectName, message.getText()));
}
return errors;
}
public List getFieldErrors() {
Message[] messages = messageContext.getMessagesByCriteria(FIELD_ERROR);
if (messages.length == 0) {
return Collections.EMPTY_LIST;
}
List errors = new ArrayList(messages.length);
for (int i = 0; i < messages.length; i++) {
Message message = messages[i];
errors.add(new FieldError(objectName, (String) message.getSource(), message.getText()));
}
return errors;
}
public Object getFieldValue(String field) {
if (mappingResults != null) {
List results = mappingResults.getResults(new FieldErrorResult(field));
if (!results.isEmpty()) {
MappingResult fieldError = (MappingResult) results.get(0);
return fieldError.getResult().getOriginalValue();
}
}
return parseFieldExpression(field).getValue(boundObject);
}
private Expression parseFieldExpression(String field) {
return expressionParser.parseExpression(field, new FluentParserContext().evaluate(boundObject.getClass()));
}
private static MessageCriteria GLOBAL_ERROR = new MessageCriteria() {
public boolean test(Message message) {
if (message.getSource() == null && message.getSeverity().equals(Severity.ERROR)) {
return true;
} else {
return false;
}
}
};
private static MessageCriteria FIELD_ERROR = new MessageCriteria() {
public boolean test(Message message) {
if (message.getSource() != null && message.getSeverity().equals(Severity.ERROR)) {
return true;
} else {
return false;
}
}
};
private static class FieldErrorResult implements MappingResultsCriteria {
private String field;
public FieldErrorResult(String field) {
this.field = field;
}
public boolean test(MappingResult result) {
if (field.equals(result.getMapping().getTargetExpression().getExpressionString())) {
return true;
} else {
return false;
}
}
}
}