add support for ConversionService use with EL parser; fully implement Errors
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user