targetClass
+ * @throws ConversionException if an exception occurred during the conversion process
+ */
+ public Object executeConversion(String converterId, Object source, Class targetClass);
+
/**
* Return the default conversion executor capable of converting source objects of the specified
* sourceClass to instances of the targetClass.
@@ -65,8 +76,8 @@ public interface ConversionService {
/**
* Return all conversion executors capable of converting from the provided sourceClass. For
- * example, getConversionExecutor(String.class) would return all converters that convert from String
- * to some other Object. Mainly useful for adapting a set of converters to some other environment.
+ * example, getConversionExecutor(String.class) would return all converters that convert from String to
+ * some other Object. Mainly useful for adapting a set of converters to some other environment.
* @param sourceClass the source class converting from
* @return the conversion executors that can convert from that source class
*/
@@ -78,4 +89,5 @@ public interface ConversionService {
* @return the class, or null if no alias exists
*/
public Class getClassForAlias(String alias);
+
}
\ No newline at end of file
diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java
index d220000c..28139444 100644
--- a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java
+++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java
@@ -386,6 +386,15 @@ public class GenericConversionService implements ConversionService {
}
}
+ public Object executeConversion(String converterId, Object source, Class targetClass) throws ConversionException {
+ if (source != null) {
+ ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass);
+ return conversionExecutor.execute(source);
+ } else {
+ return null;
+ }
+ }
+
public Class getClassForAlias(String name) throws IllegalArgumentException {
Class clazz = (Class) aliasMap.get(name);
if (clazz != null) {
diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/PropertyNotFoundException.java b/spring-binding/src/main/java/org/springframework/binding/expression/PropertyNotFoundException.java
index 5bc5b6a6..33f7466b 100644
--- a/spring-binding/src/main/java/org/springframework/binding/expression/PropertyNotFoundException.java
+++ b/spring-binding/src/main/java/org/springframework/binding/expression/PropertyNotFoundException.java
@@ -29,8 +29,7 @@ public class PropertyNotFoundException extends EvaluationException {
* @param cause root cause of the failure
*/
public PropertyNotFoundException(Class contextClass, String property, Throwable cause) {
- super(contextClass, property, "Property '" + property + "' not found on context of class ["
- + contextClass.getName() + "]", cause);
+ super(contextClass, property, "Property not found", cause);
}
}
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
index 14aba128..c7b595af 100644
--- a/spring-binding/src/main/java/org/springframework/binding/expression/ValueCoercionException.java
+++ b/spring-binding/src/main/java/org/springframework/binding/expression/ValueCoercionException.java
@@ -48,8 +48,8 @@ public class ValueCoercionException extends EvaluationException {
* @param cause root cause of the failure
*/
public ValueCoercionException(Class contextClass, String property, Object value, Class targetClass, Throwable cause) {
- super(contextClass, property, "Value [" + value + "] could not be coerced to type [" + targetClass.getName()
- + "]", cause);
+ super(contextClass, property,
+ "Value could not be converted to target class; is a suitable type converter registered?", cause);
this.value = value;
this.targetClass = targetClass;
}
diff --git a/spring-binding/src/main/java/org/springframework/binding/message/MessageContextErrors.java b/spring-binding/src/main/java/org/springframework/binding/message/MessageContextErrors.java
index f83581a7..9363034a 100644
--- a/spring-binding/src/main/java/org/springframework/binding/message/MessageContextErrors.java
+++ b/spring-binding/src/main/java/org/springframework/binding/message/MessageContextErrors.java
@@ -72,20 +72,21 @@ public class MessageContextErrors extends AbstractErrors {
}
public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
- messageContext.addMessage(new MessageBuilder().error().source(field).code(errorCode).args(errorArgs)
- .defaultText(defaultMessage).build());
+ messageContext.addMessage(new MessageBuilder().error().source(fixedField(field)).code(errorCode)
+ .args(errorArgs).defaultText(defaultMessage).build());
}
public void addAllErrors(Errors errors) {
Iterator it = errors.getAllErrors().iterator();
while (it.hasNext()) {
ObjectError error = (ObjectError) it.next();
+ MessageBuilder builder = new MessageBuilder().error().codes(error.getCodes()).args(error.getArguments())
+ .defaultText(error.getDefaultMessage());
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());
+ builder.source(fieldError.getField());
}
+ messageContext.addMessage(builder.build());
}
}
@@ -103,7 +104,7 @@ public class MessageContextErrors extends AbstractErrors {
Message message = messages[i];
errors.add(new ObjectError(objectName, message.getText()));
}
- return errors;
+ return Collections.unmodifiableList(errors);
}
public List getFieldErrors() {
@@ -116,11 +117,12 @@ public class MessageContextErrors extends AbstractErrors {
Message message = messages[i];
errors.add(new FieldError(objectName, (String) message.getSource(), message.getText()));
}
- return errors;
+ return Collections.unmodifiableList(errors);
}
public Object getFieldValue(String field) {
- // requires boundObject, and expressionParser to work
+ field = fixedField(field);
+ // requires boundObject and expressionParser to be set to work
if (mappingResults != null) {
List results = mappingResults.getResults(new PropertyErrorMappingResult(field));
if (!results.isEmpty()) {
@@ -131,13 +133,15 @@ public class MessageContextErrors extends AbstractErrors {
return parseFieldExpression(field).getValue(boundObject);
}
+ // internal helpers
+
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)) {
+ if (message.getSeverity() == Severity.ERROR && message.getSource() == null) {
return true;
} else {
return false;
@@ -147,7 +151,7 @@ public class MessageContextErrors extends AbstractErrors {
private static MessageCriteria FIELD_ERROR = new MessageCriteria() {
public boolean test(Message message) {
- if (message.getSource() != null && message.getSeverity().equals(Severity.ERROR)) {
+ if (message.getSeverity() == Severity.ERROR && message.getSource() instanceof String) {
return true;
} else {
return false;
diff --git a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java
index 056ec868..93f3c250 100644
--- a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java
+++ b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java
@@ -93,6 +93,10 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
throw new UnsupportedOperationException("Auto-generated method stub");
}
+ public Object executeConversion(String converterId, Object source, Class targetClass) {
+ throw new UnsupportedOperationException("Auto-generated method stub");
+ }
+
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionExecutionException {
throw new UnsupportedOperationException("Auto-generated method stub");
diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Amenity.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Amenity.java
index 9b1c2255..54d381e8 100644
--- a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Amenity.java
+++ b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Amenity.java
@@ -1,5 +1,31 @@
package org.springframework.webflow.samples.booking;
-public enum Amenity {
- OCEAN_VIEW, LATE_CHECKOUT, MINIBAR;
-}
+import java.io.Serializable;
+
+public class Amenity implements Serializable {
+
+ private String name;
+
+ public Amenity(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Amenity)) {
+ return false;
+ }
+ Amenity a = (Amenity) obj;
+ return name.equals(a.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java
index b921bf13..00d188ae 100644
--- a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java
+++ b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java
@@ -13,6 +13,7 @@ public class ApplicationConversionService extends DefaultConversionService {
StringToDate dateConverter = new StringToDate();
dateConverter.setPattern("MM-dd-yyyy");
addConverter("shortDate", dateConverter);
+ addConverter(new StringToAmenity());
}
}
\ No newline at end of file
diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java
index ecc1db34..267ccde4 100755
--- a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java
+++ b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java
@@ -5,6 +5,7 @@ import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
+import java.util.Iterator;
import java.util.Set;
import javax.persistence.Basic;
@@ -186,6 +187,12 @@ public class Booking implements Serializable {
}
public void setAmenities(Set- * More specifically, a typical flow execution test case will test: + * A typical flow execution test case will test: *
* A flow execution test can effectively automate and validate the orchestration required to drive an end-to-end * business task that spans several steps involving the user to complete. Such tests are a good way to test your system * top-down starting at the web-tier and pushing through all the way to the DB without having to deploy to a servlet or * portlet container. In addition, they can be used to effectively test a flow's execution (the web layer) standalone, - * typically with a mock service layer. Both styles of testing are valuable and supported. + * typically with a mock service layer. * * @author Keith Donald */ @@ -311,14 +309,15 @@ public abstract class AbstractFlowExecutionTests extends TestCase { } /** - * Assert that the entire flow execution has ended; that is, it is no longer active. + * Assert that the flow execution has ended; that is, it is no longer active. */ protected void assertFlowExecutionEnded() { assertTrue("The flow execution is still active but it should have ended", getFlowExecution().hasEnded()); } /** - * Assert that the entire flow execution has ended; that is, it is no longer active. + * Assert that the flow execution has ended with the outcome specified. + * @param outcome the name of the flow execution outcome */ protected void assertFlowExecutionOutcomeEquals(String outcome) { assertNotNull("There has been no flow execution outcome", flowExecutionOutcome); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/TestBean.java b/spring-webflow/src/test/java/org/springframework/webflow/TestBean.java index fe7b3743..875051e5 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/TestBean.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/TestBean.java @@ -78,4 +78,5 @@ public class TestBean implements Serializable { public int hashCode() { return (datum1.hashCode() + datum2 + (executed ? 1 : 0)) * 29; } + } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java index 392bb001..33a603d1 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java @@ -76,6 +76,10 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase { throw new UnsupportedOperationException("Auto-generated method stub"); } + public Object executeConversion(String converterId, Object source, Class targetClass) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) throws ConversionExecutionException { throw new UnsupportedOperationException("Auto-generated method stub"); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/BindingModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/BindingModelTests.java new file mode 100644 index 00000000..c9cd8acf --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/BindingModelTests.java @@ -0,0 +1,209 @@ +package org.springframework.webflow.mvc.view; + +import java.beans.PropertyEditor; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.springframework.binding.convert.converters.StringToObject; +import org.springframework.binding.convert.service.DefaultConversionService; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.binding.mapping.Mapping; +import org.springframework.binding.mapping.impl.DefaultMappingResults; +import org.springframework.binding.mapping.results.TypeConversionError; +import org.springframework.binding.message.DefaultMessageContext; +import org.springframework.binding.message.MessageBuilder; +import org.springframework.validation.FieldError; +import org.springframework.webflow.TestBean; +import org.springframework.webflow.engine.builder.BinderConfiguration; +import org.springframework.webflow.engine.builder.BinderConfiguration.Binding; +import org.springframework.webflow.expression.DefaultExpressionParserFactory; + +public class BindingModelTests extends TestCase { + + BindingModel model; + DefaultMessageContext messages; + DefaultConversionService conversionService; + TestBean testBean; + ExpressionParser expressionParser; + + public void setUp() { + testBean = new TestBean(); + messages = new DefaultMessageContext(); + conversionService = new DefaultConversionService(); + expressionParser = DefaultExpressionParserFactory.getExpressionParser(); + model = new BindingModel("testBean", testBean, expressionParser, conversionService, messages); + } + + public void testInitialState() { + assertEquals(0, model.getErrorCount()); + assertEquals(0, model.getFieldErrorCount()); + assertEquals(0, model.getFieldErrorCount("datum1")); + assertEquals(0, model.getGlobalErrorCount()); + assertEquals(0, model.getAllErrors().size()); + assertEquals(0, model.getFieldErrors().size()); + assertNull(model.getFieldError("datum1")); + assertEquals(String.class, model.getFieldType("datum1")); + } + + public void testGetValue() { + testBean.datum1 = "test"; + assertEquals("test", model.getFieldValue("datum1")); + } + + public void testGetConvertedValue() { + testBean.datum2 = 3; + assertEquals("3", model.getFieldValue("datum2")); + } + + public void testGetRawValue() { + testBean.datum2 = 3; + assertEquals(new Integer(3), model.getRawFieldValue("datum2")); + } + + public void testGetFieldValueNonStringNoConversionService() { + model = new BindingModel("testBean", testBean, DefaultExpressionParserFactory.getExpressionParser(), null, + messages); + testBean.datum2 = 3; + assertEquals(new Integer(3), model.getFieldValue("datum2")); + } + + public void testGetFieldValueConvertedWithCustomConverter() { + testBean.datum2 = 3; + conversionService.addConverter("customConverter", new StringToObject(Integer.class) { + protected Object toObject(String string, Class targetClass) throws Exception { + return Integer.valueOf(string); + } + + protected String toString(Object object) throws Exception { + return "$" + object; + } + }); + BinderConfiguration binder = new BinderConfiguration(); + binder.addBinding(new Binding("datum2", "customConverter", true)); + model.setBinderConfiguration(binder); + assertEquals("$3", model.getFieldValue("datum2")); + } + + public void testGetFieldValueError() { + Map source = new HashMap(); + source.put("datum2", "bogus"); + List mappingResults = new ArrayList(); + Mapping mapping = new Mapping() { + public Expression getSourceExpression() { + return expressionParser.parseExpression("datum2", null); + } + + public Expression getTargetExpression() { + return expressionParser.parseExpression("datum2", null); + } + + public boolean isRequired() { + return true; + } + }; + mappingResults.add(new TypeConversionError(mapping, "bogus", null)); + DefaultMappingResults results = new DefaultMappingResults(source, testBean, mappingResults); + model.setMappingResults(results); + assertEquals("bogus", model.getFieldValue("datum2")); + // not offically an error until an actual error message is associated with field + assertEquals(0, model.getErrorCount()); + assertEquals(0, model.getFieldErrorCount()); + } + + public void testGetFieldError() { + messages.addMessage(new MessageBuilder().source("datum2").error().defaultText("Error").build()); + assertEquals(1, model.getErrorCount()); + assertEquals(1, model.getFieldErrorCount()); + assertEquals(0, model.getGlobalErrorCount()); + + FieldError error = model.getFieldError("datum2"); + assertEquals(null, error.getCode()); + assertEquals(null, error.getCodes()); + assertEquals(null, error.getArguments()); + assertEquals("Error", error.getDefaultMessage()); + // we dont track this + assertEquals(null, error.getRejectedValue()); + assertTrue(!error.isBindingFailure()); + + FieldError error2 = (FieldError) model.getFieldErrors().get(0); + assertEquals(error, error2); + } + + public void testGetFieldErrorsWildcard() { + messages.addMessage(new MessageBuilder().source("datum2").error().defaultText("Error").build()); + assertEquals(1, model.getFieldErrorCount("da*")); + FieldError error = model.getFieldError("da*"); + assertEquals(null, error.getCode()); + assertEquals(null, error.getCodes()); + assertEquals(null, error.getArguments()); + assertEquals("Error", error.getDefaultMessage()); + } + + public void testFindPropertyEditor() { + PropertyEditor editor = model.findEditor("datum2", Integer.class); + assertNotNull(editor); + editor.setAsText((String) model.getFieldValue("datum2")); + assertEquals("0", editor.getAsText()); + } + + public void testNestedPath() { + model = new BindingModel("nestedPathBean", new NestedPathBean(), expressionParser, conversionService, messages); + model.pushNestedPath("nestedBean"); + assertEquals("test", model.getFieldValue("datum1")); + assertEquals("0", model.getFieldValue("datum2")); + assertEquals(int.class, model.getFieldType("datum2")); + + messages.addMessage(new MessageBuilder().source("nestedBean.datum2").error().defaultText("Error").build()); + assertNotNull(model.getFieldErrors("datum2").get(0)); + model.popNestedPath(); + assertEquals("", model.getFieldValue("datum1")); + } + + public static class NestedPathBean { + private String datum1 = ""; + + private NestedBean nestedBean = new NestedBean(); + + public String getDatum1() { + return datum1; + } + + public void setDatum1(String datum1) { + this.datum1 = datum1; + } + + public NestedBean getNestedBean() { + return nestedBean; + } + + public void setNestedBean(NestedBean nestedBean) { + this.nestedBean = nestedBean; + } + + public static class NestedBean { + private String datum1 = "test"; + private int datum2; + + public int getDatum2() { + return datum2; + } + + public void setDatum2(int datum2) { + this.datum2 = datum2; + } + + public String getDatum1() { + return datum1; + } + + public void setDatum1(String datum1) { + this.datum1 = datum1; + } + } + } +}