diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ValueCoercionException.java b/spring-binding/src/main/java/org/springframework/binding/expression/ValueCoercionException.java new file mode 100644 index 00000000..b1ef3a39 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ValueCoercionException.java @@ -0,0 +1,72 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.binding.expression; + +import org.springframework.core.style.StylerUtils; + +/** + * An evaluation exception indicating a expression failed to evaluate because the source value could not be coerced to + * the target class + * @author Scott Andrews + */ +public class ValueCoercionException extends EvaluationException { + + private Object value; + private Class targetClass; + + /** + * Creates a new property not found exception + * @param contextClass the class of object upon which coercion was attempted + * @param property the property that could not be coerced + * @param value the value that could not be coerced + * @param targetClass the class the value could not be coerced to + */ + public ValueCoercionException(Class contextClass, String property, Object value, Class targetClass) { + this(contextClass, property, value, targetClass, null); + this.value = value; + this.targetClass = targetClass; + } + + /** + * Creates a new property not found exception + * @param contextClass the class of object upon which coercion was attempted + * @param property the property that could not be coerced + * @param value the value that could not be coerced + * @param targetClass the class the value could not be coerced to + * @param cause root cause of the failure + */ + public ValueCoercionException(Class contextClass, String property, Object value, Class targetClass, Throwable cause) { + super(contextClass, property, "value " + StylerUtils.style(value) + " could not be coerced to [" + + targetClass.getName() + "]", cause); + this.value = value; + this.targetClass = targetClass; + } + + /** + * @return the value that could not be coerced + */ + public Object getValue() { + return value; + } + + /** + * @return the class the value could not be coerced to + */ + public Class getTargetClass() { + return targetClass; + } + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java index 3e7a6c22..9364adf8 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java @@ -23,12 +23,14 @@ import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.BeansException; import org.springframework.beans.NotReadablePropertyException; import org.springframework.beans.NotWritablePropertyException; +import org.springframework.beans.TypeMismatchException; import org.springframework.binding.convert.ConversionException; 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; +import org.springframework.binding.expression.ValueCoercionException; /** * An expression that delegates to a {@link BeanWrapperImpl bean wrapper} to evaluate or set a property of a context. @@ -45,6 +47,7 @@ import org.springframework.binding.expression.PropertyNotFoundException; * support method invocation, arithmetic operations, or logic operations. * * @author Keith Donald + * @author Scott Andrews */ public class BeanWrapperExpression implements Expression { @@ -99,6 +102,8 @@ public class BeanWrapperExpression implements Expression { beanWrapper.setPropertyValue(expression, value); } catch (NotWritablePropertyException e) { throw new PropertyNotFoundException(context.getClass(), expression, e); + } catch (TypeMismatchException e) { + throw new ValueCoercionException(context.getClass(), expression, value, e.getRequiredType(), e); } catch (BeansException e) { throw new EvaluationException(context.getClass(), getExpressionString(), "A BeansException occurred setting the value of expression '" + getExpressionString() diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/BindingValueExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/BindingValueExpression.java index 37db9067..daa6dd04 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/el/BindingValueExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/BindingValueExpression.java @@ -7,9 +7,7 @@ import javax.el.PropertyNotFoundException; import javax.el.PropertyNotWritableException; import javax.el.ValueExpression; -import org.springframework.binding.convert.ConversionException; import org.springframework.binding.convert.ConversionService; -import org.springframework.core.style.StylerUtils; import org.springframework.util.Assert; /** @@ -93,12 +91,7 @@ class BindingValueExpression extends ValueExpression { if (expectedType == null) { return value; } else { - try { - return conversionService.executeConversion(value, expectedType); - } catch (ConversionException e) { - throw new ELException("Unable to coerce value " + StylerUtils.style(value) + " to expected type [" - + expectedType + "]", e); - } + return conversionService.executeConversion(value, expectedType); } } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java index 54c05364..8c20b213 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java @@ -19,15 +19,18 @@ import javax.el.ELContext; import javax.el.ELException; import javax.el.ValueExpression; +import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.PropertyNotFoundException; +import org.springframework.binding.expression.ValueCoercionException; import org.springframework.util.Assert; /** * Evaluates a parsed EL expression. * * @author Jeremy Grelle + * @author Scott Andrews */ public class ELExpression implements Expression { @@ -82,6 +85,8 @@ public class ELExpression implements Expression { } } catch (javax.el.PropertyNotFoundException e) { throw new PropertyNotFoundException(context.getClass(), getExpressionString(), e); + } catch (ConversionExecutionException e) { + throw new ValueCoercionException(context.getClass(), getExpressionString(), value, e.getTargetClass(), e); } catch (ELException e) { throw new EvaluationException(context.getClass(), getExpressionString(), "An ELException occurred setting the value of expression '" + getExpressionString() diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java index 78ec31a6..3849a880 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java @@ -29,15 +29,18 @@ import ognl.TypeConverter; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.BeansException; import org.springframework.beans.InvalidPropertyException; +import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.PropertyNotFoundException; +import org.springframework.binding.expression.ValueCoercionException; /** * Evaluates a parsed Ognl expression. * * @author Keith Donald + * @author Scott Andrews */ class OgnlExpression implements Expression { @@ -97,9 +100,15 @@ class OgnlExpression implements Expression { } catch (NoSuchPropertyException e) { throw new PropertyNotFoundException(context.getClass(), getExpressionString(), e); } catch (OgnlException e) { - throw new EvaluationException(context.getClass(), getExpressionString(), - "An OgnlException occurred setting the value of expression '" + getExpressionString() - + "' on context [" + context.getClass() + "] to [" + value + "]", e); + if (e.getReason() instanceof ConversionExecutionException) { + ConversionExecutionException conversionEx = (ConversionExecutionException) e.getReason(); + throw new ValueCoercionException(context.getClass(), expressionString, value, conversionEx + .getTargetClass(), conversionEx); + } else { + throw new EvaluationException(context.getClass(), getExpressionString(), + "An OgnlException occurred setting the value of expression '" + getExpressionString() + + "' on context [" + context.getClass() + "] to [" + value + "]", e.getReason()); + } } } diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java index 5b15be97..23b47534 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java @@ -19,6 +19,7 @@ import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ValueCoercionException; import org.springframework.binding.mapping.Mapping; import org.springframework.util.Assert; @@ -27,6 +28,7 @@ import org.springframework.util.Assert; * a source object to a property on a target object, optionally applying a type conversion during the mapping process. * * @author Keith Donald + * @author Scott Andrews */ public class DefaultMapping implements Mapping { @@ -132,6 +134,8 @@ public class DefaultMapping implements Mapping { try { targetExpression.setValue(context.getTarget(), targetValue); context.setSuccessResult(sourceValue, targetValue); + } catch (ValueCoercionException e) { + context.setTypeConversionErrorResult(e); } catch (EvaluationException e) { context.setTargetAccessError(sourceValue, e); } diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingContext.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingContext.java index 5e8fcd4d..681c6c59 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingContext.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingContext.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.expression.ValueCoercionException; import org.springframework.binding.mapping.Mapping; import org.springframework.binding.mapping.MappingResult; import org.springframework.binding.mapping.MappingResults; @@ -34,6 +35,7 @@ import org.springframework.binding.mapping.results.TypeConversionError; /** * Default mapping context implementation. * @author Keith Donald + * @author Scott Andrews */ public class DefaultMappingContext { @@ -117,6 +119,17 @@ public class DefaultMappingContext { add(new MappingResult(currentMapping, new TypeConversionError(exception))); } + /** + * Indicates the current mapping ended with a 'type conversion' error. This means the value obtained from the source + * could not be converted to a type that could be assigned to the target expression. + * @param exception the coercion exception that occurred, containing the original source value that could not be + * converted during the mapping attempt, as well as the desired target type to which conversion could not be + * performed + */ + public void setTypeConversionErrorResult(ValueCoercionException exception) { + add(new MappingResult(currentMapping, new TypeConversionError(exception))); + } + /** * Indicates a error occurred accessing the source mapping expression. * @param error the error that occurred diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java index 8eaaec6e..2ac291f4 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java @@ -16,6 +16,7 @@ package org.springframework.binding.mapping.results; import org.springframework.binding.convert.ConversionExecutionException; +import org.springframework.binding.expression.ValueCoercionException; import org.springframework.binding.mapping.Result; import org.springframework.core.style.ToStringCreator; @@ -23,6 +24,7 @@ import org.springframework.core.style.ToStringCreator; * Indicates a type conversion occurred during a mapping operation. * * @author Keith Donald + * @author Scott Andrews */ public class TypeConversionError extends Result { @@ -30,7 +32,7 @@ public class TypeConversionError extends Result { private Class targetType; - private ConversionExecutionException exception; + private Exception exception; /** * Creates a new type conversion error. @@ -38,10 +40,18 @@ public class TypeConversionError extends Result { */ public TypeConversionError(ConversionExecutionException exception) { this.exception = exception; + this.originalValue = exception.getValue(); + this.targetType = exception.getTargetClass(); + } + + public TypeConversionError(ValueCoercionException exception) { + this.exception = exception; + this.originalValue = exception.getValue(); + this.targetType = exception.getTargetClass(); } public Object getOriginalValue() { - return exception.getValue(); + return originalValue; } public Object getMappedValue() { @@ -62,13 +72,13 @@ public class TypeConversionError extends Result { * Returns the target type of the conversion attempt. */ public Class getTargetClass() { - return exception.getTargetClass(); + return targetType; } /** * Returns the backing type conversion exception that occurred. */ - public ConversionExecutionException getException() { + public Exception getException() { return exception; } diff --git a/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java b/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java index 58fdc9f1..7a80aec4 100644 --- a/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java @@ -18,9 +18,9 @@ package org.springframework.binding.expression.beanwrapper; import junit.framework.TestCase; import org.springframework.beans.TypeMismatchException; -import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ParserException; +import org.springframework.binding.expression.ValueCoercionException; import org.springframework.binding.expression.ognl.TestBean; import org.springframework.binding.expression.support.FluentParserContext; @@ -108,8 +108,9 @@ public class BeanWrapperExpressionParserTests extends TestCase { try { e.setValue(bean, "bogus"); fail("Should have failed tme"); - } catch (EvaluationException ex) { + } catch (ValueCoercionException ex) { assertTrue(ex.getCause() instanceof TypeMismatchException); } } + } \ No newline at end of file diff --git a/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java b/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java index 47bff70b..41f6061c 100644 --- a/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java @@ -16,6 +16,7 @@ import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionVariable; import org.springframework.binding.expression.ParserException; +import org.springframework.binding.expression.ValueCoercionException; import org.springframework.binding.expression.support.FluentParserContext; public class ELExpressionParserTests extends TestCase { @@ -166,8 +167,7 @@ public class ELExpressionParserTests extends TestCase { try { exp.setValue(context, "bogus"); fail("Should have failed with coersion"); - } catch (EvaluationException e) { - + } catch (ValueCoercionException e) { } } diff --git a/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java b/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java index 04a1611b..53a6d50f 100644 --- a/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java @@ -17,10 +17,10 @@ package org.springframework.binding.expression.ognl; import junit.framework.TestCase; -import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionVariable; import org.springframework.binding.expression.ParserException; +import org.springframework.binding.expression.ValueCoercionException; import org.springframework.binding.expression.support.FluentParserContext; public class OgnlExpressionParserTests extends TestCase { @@ -184,7 +184,8 @@ public class OgnlExpressionParserTests extends TestCase { try { e.setValue(bean, "bogus"); fail("Should have failed tme"); - } catch (EvaluationException ex) { + } catch (ValueCoercionException ex) { } } + } \ No newline at end of file diff --git a/spring-binding/src/test/java/org/springframework/binding/mapping/DefaultMapperTests.java b/spring-binding/src/test/java/org/springframework/binding/mapping/DefaultMapperTests.java index 5cb22980..741870f5 100644 --- a/spring-binding/src/test/java/org/springframework/binding/mapping/DefaultMapperTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/mapping/DefaultMapperTests.java @@ -11,6 +11,7 @@ import org.springframework.binding.expression.el.DefaultExpressionFactoryUtils; import org.springframework.binding.expression.el.ELExpressionParser; import org.springframework.binding.mapping.impl.DefaultMapper; import org.springframework.binding.mapping.impl.DefaultMapping; +import org.springframework.binding.mapping.results.TypeConversionError; public class DefaultMapperTests extends TestCase { private DefaultMapper mapper = new DefaultMapper(); @@ -65,7 +66,8 @@ public class DefaultMapperTests extends TestCase { bean1.put("boop", "bogus"); TestBean2 bean2 = new TestBean2(); MappingResults results = mapper.map(bean1, bean2); - assertTrue(results.hasErrorResults()); + assertEquals(TypeConversionError.class, ((MappingResult) results.getErrorResults().get(0)).getResult() + .getClass()); } public static class TestBean {