numerous binding enhancements; removed lazy binding for time being

This commit is contained in:
Keith Donald
2009-07-08 21:43:35 +00:00
parent 5ff6191d72
commit 2bbf827d57
21 changed files with 805 additions and 757 deletions

View File

@@ -2,7 +2,6 @@ package org.springframework.ui.binding.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
@@ -23,6 +22,8 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.MissingSourceValuesException;
import org.springframework.ui.binding.NoSuchBindingException;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.date.DateFormatter;
@@ -31,33 +32,39 @@ import org.springframework.ui.format.number.CurrencyFormatter;
import org.springframework.ui.format.number.IntegerFormatter;
import org.springframework.ui.message.MockMessageSource;
import edu.emory.mathcs.backport.java.util.Arrays;
public class GenericBinderTests {
private TestBean bean;
private GenericBinder binder;
@Before
public void setUp() {
bean = new TestBean();
binder = new GenericBinder(bean);
LocaleContextHolder.setLocale(Locale.US);
}
@After
public void tearDown() {
LocaleContextHolder.setLocale(null);
}
@Test
public void bindSingleValuesWithDefaultTypeConverterConversion() {
binder.addBinding("string");
binder.addBinding("integer");
binder.addBinding("foo");
Map<String, String> values = new LinkedHashMap<String, String>();
values.put("string", "test");
values.put("integer", "3");
values.put("foo", "BAR");
BindingResults results = binder.bind(values);
assertEquals(3, results.size());
assertEquals("string", results.get(0).getProperty());
assertFalse(results.get(0).isFailure());
assertEquals("test", results.get(0).getSourceValue());
@@ -69,14 +76,18 @@ public class GenericBinderTests {
assertEquals("foo", results.get(2).getProperty());
assertFalse(results.get(2).isFailure());
assertEquals("BAR", results.get(2).getSourceValue());
assertEquals("test", bean.getString());
assertEquals(3, bean.getInteger());
assertEquals(FooEnum.BAR, bean.getFoo());
}
@Test
public void bindSingleValuesWithDefaultTypeCoversionFailure() {
public void bindSingleValuesWithDefaultTypeConversionFailure() {
binder.addBinding("string");
binder.addBinding("integer");
binder.addBinding("foo");
Map<String, String> values = new LinkedHashMap<String, String>();
values.put("string", "test");
// bad value
@@ -90,118 +101,72 @@ public class GenericBinderTests {
@Test
public void bindSingleValuePropertyFormatter() throws ParseException {
binder.configureBinding(new BindingConfiguration("date", new DateFormatter()));
binder.addBinding("date").formatWith(new DateFormatter());
binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
}
@Test
public void bindSingleValuePropertyFormatterParseException() {
binder.configureBinding(new BindingConfiguration("date", new DateFormatter()));
binder.addBinding("date").formatWith(new DateFormatter());
binder.bind(Collections.singletonMap("date", "bogus"));
}
@Test
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
binder.addBinding("date");
binder.registerFormatter(Date.class, new DateFormatter());
binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
}
@Test
public void bindSingleValueWithFormatterRegisteredByAnnotation() throws ParseException {
binder.addBinding("currency");
GenericFormatterRegistry registry = new GenericFormatterRegistry();
registry.add(CurrencyFormat.class, new CurrencyFormatter());
binder.setFormatterRegistry(registry);
binder.bind(Collections.singletonMap("currency", "$23.56"));
assertEquals(new BigDecimal("23.56"), bean.getCurrency());
}
@Test
public void bindSingleValueWithnAnnotationFormatterFactoryRegistered() throws ParseException {
public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException {
binder.addBinding("currency");
binder.registerFormatterFactory(new CurrencyAnnotationFormatterFactory());
binder.bind(Collections.singletonMap("currency", "$23.56"));
assertEquals(new BigDecimal("23.56"), bean.getCurrency());
}
@Test
@Test(expected = NoSuchBindingException.class)
public void bindSingleValuePropertyNotFound() throws ParseException {
BindingResults results = binder.bind(Collections.singletonMap("bogus", "2009-06-01"));
assertEquals(1, results.size());
assertTrue(results.get(0).isFailure());
assertEquals("propertyNotFound", results.get(0).getAlert().getCode());
binder.bind(Collections.singletonMap("bogus", "2009-06-01"));
}
@Test
public void bindUserValuesCreatedFromUserMap() {
@Test(expected=MissingSourceValuesException.class)
public void bindMissingRequiredSourceValue() {
binder.addBinding("string");
binder.addBinding("integer").required();
Map<String, String> userMap = new LinkedHashMap<String, String>();
userMap.put("string", "test");
userMap.put("integer", "3");
BindingResults results = binder.bind(userMap);
assertEquals(2, results.size());
assertEquals("test", results.get(0).getSourceValue());
assertEquals("3", results.get(1).getSourceValue());
assertEquals("test", bean.getString());
assertEquals(3, bean.getInteger());
}
@Test
public void getBindingOptimistic() {
Binding b = binder.getBinding("integer");
assertFalse(b.isCollection());
assertEquals("0", b.getValue());
BindingResult result = b.setValue("5");
assertEquals("5", b.getValue());
assertFalse(result.isFailure());
}
@Test
public void getBindingStrict() {
binder.setStrict(true);
Binding b = binder.getBinding("integer");
assertNull(b);
binder.configureBinding(new BindingConfiguration("integer", null));
b = binder.getBinding("integer");
assertFalse(b.isCollection());
assertEquals("0", b.getValue());
BindingResult result = b.setValue("5");
assertEquals("5", b.getValue());
assertFalse(result.isFailure());
}
@Test
public void bindStrictNoMappingBindings() {
binder.setStrict(true);
binder.configureBinding(new BindingConfiguration("integer", null));
Map<String, String> values = new LinkedHashMap<String, String>();
values.put("integer", "3");
values.put("foo", "BAR");
BindingResults results = binder.bind(values);
assertEquals(2, results.size());
assertEquals("integer", results.get(0).getProperty());
assertFalse(results.get(0).isFailure());
assertEquals("3", results.get(0).getSourceValue());
assertEquals("foo", results.get(1).getProperty());
assertTrue(results.get(1).isFailure());
assertEquals("BAR", results.get(1).getSourceValue());
// missing "integer"
binder.bind(userMap);
}
@Test
public void getBindingCustomFormatter() {
binder.configureBinding(new BindingConfiguration("currency", new CurrencyFormatter()));
binder.addBinding("currency").formatWith(new CurrencyFormatter());
Binding b = binder.getBinding("currency");
assertFalse(b.isCollection());
assertEquals("", b.getValue());
b.setValue("$23.56");
assertEquals("$23.56", b.getValue());
}
@Test
public void getBindingCustomFormatterRequiringTypeCoersion() {
// IntegerFormatter formats Longs, so conversion from Integer -> Long is performed
binder.configureBinding(new BindingConfiguration("integer", new IntegerFormatter()));
binder.addBinding("integer").formatWith(new IntegerFormatter());
Binding b = binder.getBinding("integer");
b.setValue("2,300");
assertEquals("2,300", b.getValue());
@@ -210,16 +175,19 @@ public class GenericBinderTests {
@Test
public void invalidFormatBindingResultCustomAlertMessage() {
MockMessageSource messages = new MockMessageSource();
messages.addMessage("invalidFormat", Locale.US, "Please enter an integer in format ### for the #{label} field; you entered #{value}");
messages.addMessage("invalidFormat", Locale.US,
"Please enter an integer in format ### for the #{label} field; you entered #{value}");
binder.setMessageSource(messages);
binder.configureBinding(new BindingConfiguration("integer", new IntegerFormatter()));
binder.addBinding("integer").formatWith(new IntegerFormatter());
Binding b = binder.getBinding("integer");
BindingResult result = b.setValue("bogus");
assertEquals("Please enter an integer in format ### for the integer field; you entered bogus", result.getAlert().getMessage());
assertEquals("Please enter an integer in format ### for the integer field; you entered bogus", result
.getAlert().getMessage());
}
@Test
public void getBindingMultiValued() {
binder.addBinding("foos");
Binding b = binder.getBinding("foos");
assertTrue(b.isCollection());
assertEquals(0, b.getCollectionValues().length);
@@ -234,8 +202,20 @@ public class GenericBinderTests {
assertEquals("BOOP", values[2]);
}
@Test
public void getBindingMultiValuedIndexAccess() {
binder.addBinding("foos");
bean.setFoos(Arrays.asList(new FooEnum[] { FooEnum.BAR }));
Binding b = binder.getBinding("foos[0]");
assertFalse(b.isCollection());
assertEquals("BAR", b.getValue());
b.setValue("BAZ");
assertEquals("BAZ", b.getValue());
}
@Test
public void getBindingMultiValuedTypeConversionFailure() {
binder.addBinding("foos");
Binding b = binder.getBinding("foos");
assertTrue(b.isCollection());
assertEquals(0, b.getCollectionValues().length);
@@ -243,18 +223,23 @@ public class GenericBinderTests {
assertTrue(result.isFailure());
assertEquals("conversionFailed", result.getAlert().getCode());
}
@Test
public void bindHandleNullValueInNestedPath() {
binder.addBinding("addresses.street");
binder.addBinding("addresses.city");
binder.addBinding("addresses.state");
binder.addBinding("addresses.zip");
Map<String, String> values = new LinkedHashMap<String, String>();
// EL configured with some options from SpelExpressionParserConfiguration:
// (see where Binder creates the parser)
// - new addresses List is created if null
// - new entries automatically built if List is currently too short - all new entries
// are new instances of the type of the list entry, they are not null.
// not currently doing anything for maps or arrays
values.put("addresses[0].street", "4655 Macy Lane");
values.put("addresses[0].city", "Melbourne");
values.put("addresses[0].state", "FL");
@@ -281,7 +266,7 @@ public class GenericBinderTests {
@Test
public void formatPossibleValue() {
binder.configureBinding(new BindingConfiguration("currency", new CurrencyFormatter()));
binder.addBinding("currency").formatWith(new CurrencyFormatter());
Binding b = binder.getBinding("currency");
assertEquals("$5.00", b.format(new BigDecimal("5")));
}
@@ -298,7 +283,7 @@ public class GenericBinderTests {
private BigDecimal currency;
private List<FooEnum> foos;
private List<Address> addresses;
public String getString() {
return string;
}
@@ -355,7 +340,7 @@ public class GenericBinderTests {
public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
}
public static class Address {
@@ -364,50 +349,51 @@ public class GenericBinderTests {
private String state;
private String zip;
private String country;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
public static class CurrencyAnnotationFormatterFactory implements AnnotationFormatterFactory<CurrencyFormat, BigDecimal> {
public static class CurrencyAnnotationFormatterFactory implements
AnnotationFormatterFactory<CurrencyFormat, BigDecimal> {
public Formatter<BigDecimal> getFormatter(CurrencyFormat annotation) {
return new CurrencyFormatter();
}

View File

@@ -37,10 +37,15 @@ public class WebBinderTests {
@Test
public void bindUserValuesCreatedFromUserMap() throws ParseException {
binder.addBinding("string");
binder.addBinding("integer");
binder.addBinding("date").formatWith(new DateFormatter());
binder.addBinding("bool");
binder.addBinding("currency");
binder.addBinding("addresses");
GenericFormatterRegistry registry = new GenericFormatterRegistry();
registry.add(CurrencyFormat.class, new CurrencyFormatter());
binder.setFormatterRegistry(registry);
binder.configureBinding(new BindingConfiguration("date", new DateFormatter()));
Map<String, String> userMap = new LinkedHashMap<String, String>();
userMap.put("string", "test");
userMap.put("_integer", "doesn't matter");

View File

@@ -3,10 +3,8 @@ package org.springframework.ui.lifecycle;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -16,18 +14,18 @@ import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Alerts;
import org.springframework.ui.alert.Severity;
import org.springframework.ui.alert.support.DefaultAlertContext;
import org.springframework.ui.binding.Binder;
import org.springframework.ui.binding.Bound;
import org.springframework.ui.binding.Model;
import org.springframework.ui.binding.support.WebBinderFactory;
import org.springframework.ui.binding.support.WebBinder;
import org.springframework.ui.format.number.CurrencyFormat;
import org.springframework.ui.validation.ValidationResult;
import org.springframework.ui.validation.ValidationResults;
import org.springframework.ui.validation.ValidationFailure;
import org.springframework.ui.validation.Validator;
import org.springframework.ui.validation.constraint.Impact;
import org.springframework.ui.validation.constraint.Message;
import org.springframework.ui.validation.constraint.ValidationConstraint;
import edu.emory.mathcs.backport.java.util.Collections;
public class BindAndValidateLifecycleTests {
private BindAndValidateLifecycle lifecycle;
@@ -40,20 +38,23 @@ public class BindAndValidateLifecycleTests {
public void setUp() {
model = new TestBean();
alertContext = new DefaultAlertContext();
Binder binder = new WebBinderFactory().getBinder(model);
Validator<TestBean> validator = new TestBeanValidator();
WebBinder binder = new WebBinder(model);
binder.addBinding("string");
binder.addBinding("integer");
binder.addBinding("foo");
Validator validator = new TestBeanValidator();
lifecycle = new BindAndValidateLifecycle(binder, validator, alertContext);
}
static class TestBeanValidator implements Validator<TestBean> {
public ValidationResults validate(TestBean model, List<String> properties) {
TestValidationResults results = new TestValidationResults();
static class TestBeanValidator implements Validator {
public List<ValidationFailure> validate(Object model, List<String> properties) {
TestBean bean = (TestBean) model;
RequiredConstraint required = new RequiredConstraint();
boolean valid = required.validate(model.getString());
boolean valid = required.validate(bean);
if (!valid) {
}
return results;
return Collections.emptyList();
}
}
@@ -111,21 +112,9 @@ public class BindAndValidateLifecycleTests {
}
}
}
static class TestValidationResults implements ValidationResults {
private List<ValidationResult> results = new ArrayList<ValidationResult>();
public void add(ValidationResult result) {
results.add(result);
}
public Iterator<ValidationResult> iterator() {
return results.iterator();
}
}
static class TestValidationFailure implements ValidationResult {
static class TestValidationFailure implements ValidationFailure {
private String property;
@@ -143,10 +132,6 @@ public class BindAndValidateLifecycleTests {
return Alerts.error(message);
}
public boolean isFailure() {
return true;
}
}
@Test
@@ -292,7 +277,7 @@ public class BindAndValidateLifecycleTests {
}
@Model(value="testBean", strictBinding=true)
@Model(value="testBean")
public class TestAnnotatedBean {
private String editable;