Browse Source

property not found / required

conversation
Keith Donald 16 years ago
parent
commit
f39f91701d
  1. 46
      org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java
  2. 23
      org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyNotFoundException.java
  3. 64
      org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyNotFoundResult.java
  4. 50
      org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java

46
org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java

@ -19,18 +19,20 @@ import java.beans.BeanInfo;
import java.beans.IntrospectionException; import java.beans.IntrospectionException;
import java.beans.Introspector; import java.beans.Introspector;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.convert.TypeConverter; import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultTypeConverter; import org.springframework.core.convert.support.DefaultTypeConverter;
import org.springframework.ui.binding.Binder; import org.springframework.ui.binding.Binder;
import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingResult; import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults; import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.MissingSourceValuesException;
import org.springframework.ui.binding.Binding.BindingStatus; import org.springframework.ui.binding.Binding.BindingStatus;
import org.springframework.ui.binding.config.BindingRuleConfiguration; import org.springframework.ui.binding.config.BindingRuleConfiguration;
import org.springframework.ui.binding.config.Condition; import org.springframework.ui.binding.config.Condition;
@ -57,6 +59,8 @@ public class GenericBinder implements Binder {
private MessageSource messageSource; private MessageSource messageSource;
private String[] requiredProperties = new String[0];
/** /**
* Creates a new binder for the model object. * Creates a new binder for the model object.
* @param model the model object containing properties this binder will bind to * @param model the model object containing properties this binder will bind to
@ -140,10 +144,15 @@ public class GenericBinder implements Binder {
public BindingResults bind(Map<String, ? extends Object> sourceValues) { public BindingResults bind(Map<String, ? extends Object> sourceValues) {
sourceValues = filter(sourceValues); sourceValues = filter(sourceValues);
checkRequired(sourceValues);
ArrayListBindingResults results = new ArrayListBindingResults(sourceValues.size()); ArrayListBindingResults results = new ArrayListBindingResults(sourceValues.size());
for (Map.Entry<String, ? extends Object> sourceValue : sourceValues.entrySet()) { for (Map.Entry<String, ? extends Object> sourceValue : sourceValues.entrySet()) {
Binding binding = getBinding(sourceValue.getKey()); try {
results.add(bind(sourceValue, binding)); Binding binding = getBinding(sourceValue.getKey());
results.add(bind(sourceValue, binding));
} catch (PropertyNotFoundException e) {
results.add(new PropertyNotFoundResult(sourceValue.getKey(), sourceValue.getValue(), messageSource));
}
} }
return results; return results;
} }
@ -163,6 +172,24 @@ public class GenericBinder implements Binder {
} }
// internal helpers // internal helpers
private void checkRequired(Map<String, ? extends Object> sourceValues) {
List<String> missingRequired = new ArrayList<String>();
for (String required : requiredProperties) {
boolean found = false;
for (String property : sourceValues.keySet()) {
if (property.equals(required)) {
found = true;
}
}
if (!found) {
missingRequired.add(required);
}
}
if (!missingRequired.isEmpty()) {
throw new MissingSourceValuesException(missingRequired, sourceValues);
}
}
private GenericBindingRule getBindingRule(String property) { private GenericBindingRule getBindingRule(String property) {
GenericBindingRule rule = bindingRules.get(property); GenericBindingRule rule = bindingRules.get(property);
@ -217,6 +244,10 @@ public class GenericBinder implements Binder {
// implementing BindingContext // implementing BindingContext
public MessageSource getMessageSource() {
return messageSource;
}
public TypeConverter getTypeConverter() { public TypeConverter getTypeConverter() {
return typeConverter; return typeConverter;
} }
@ -326,8 +357,7 @@ public class GenericBinder implements Binder {
return propDesc; return propDesc;
} }
} }
throw new IllegalArgumentException("No property '" + property + "' found on model [" throw new PropertyNotFoundException(property, modelClass);
+ modelClass.getName() + "]");
} }
private BeanInfo getBeanInfo(Class<?> clazz) { private BeanInfo getBeanInfo(Class<?> clazz) {
@ -341,6 +371,8 @@ public class GenericBinder implements Binder {
public interface BindingContext { public interface BindingContext {
MessageSource getMessageSource();
TypeConverter getTypeConverter(); TypeConverter getTypeConverter();
Condition getEditableCondition(); Condition getEditableCondition();
@ -362,4 +394,8 @@ public class GenericBinder implements Binder {
} }
public void setRequired(String[] propertyPaths) {
this.requiredProperties = propertyPaths;
}
} }

23
org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyNotFoundException.java

@ -0,0 +1,23 @@
package org.springframework.ui.binding.support;
public class PropertyNotFoundException extends RuntimeException {
private String property;
private Class<?> modelClass;
public PropertyNotFoundException(String property, Class<?> modelClass) {
super("No property '" + property + "' found on model [" + modelClass.getName() + "]");
this.property = property;
this.modelClass = modelClass;
}
public String getProperty() {
return property;
}
public Class<?> getModelClass() {
return modelClass;
}
}

64
org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyNotFoundResult.java

@ -0,0 +1,64 @@
package org.springframework.ui.binding.support;
import org.springframework.context.MessageSource;
import org.springframework.core.style.StylerUtils;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument;
class PropertyNotFoundResult implements BindingResult {
private String property;
private Object sourceValue;
private MessageSource messageSource;
public PropertyNotFoundResult(String property, Object sourceValue, MessageSource messageSource) {
this.property = property;
this.sourceValue = sourceValue;
this.messageSource = messageSource;
}
public String getProperty() {
return property;
}
public Object getSourceValue() {
return sourceValue;
}
public boolean isFailure() {
return true;
}
public Alert getAlert() {
return new Alert() {
public String getCode() {
return "propertyNotFound";
}
public Severity getSeverity() {
return Severity.WARNING;
}
public String getMessage() {
MessageBuilder builder = new MessageBuilder(messageSource);
builder.code("bindSuccess");
builder.arg("label", new ResolvableArgument(property));
builder.arg("value", sourceValue);
// TODO lazily create default message
builder.defaultMessage("Successfully bound user value " + StylerUtils.style(sourceValue)
+ " to property '" + property + "'");
return builder.build();
}
};
}
public String toString() {
return getAlert().toString();
}
}

50
org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java

@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -16,23 +17,33 @@ import java.util.Map;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.i18n.LocaleContextHolder; 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.BindingResults;
import org.springframework.ui.binding.MissingSourceValuesException;
import org.springframework.ui.format.AnnotationFormatterFactory; import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatted; import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.date.DateFormatter; import org.springframework.ui.format.date.DateFormatter;
import org.springframework.ui.format.number.CurrencyFormat; import org.springframework.ui.format.number.CurrencyFormat;
import org.springframework.ui.format.number.CurrencyFormatter; import org.springframework.ui.format.number.CurrencyFormatter;
import org.springframework.ui.format.number.IntegerFormatter;
import org.springframework.ui.message.MockMessageSource;
import org.springframework.util.Assert;
public class GenericBinderTests { public class GenericBinderTests {
private GenericBinder binder;
private TestBean bean; private TestBean bean;
@Before @Before
public void setUp() { public void setUp() {
bean = new TestBean(); bean = new TestBean();
binder = new GenericBinder(bean);
LocaleContextHolder.setLocale(Locale.US); LocaleContextHolder.setLocale(Locale.US);
} }
@ -77,7 +88,6 @@ public class GenericBinderTests {
@Test @Test
public void bindSingleValuesWithDefaultTypeConversionFailure() { public void bindSingleValuesWithDefaultTypeConversionFailure() {
GenericBinder binder = new GenericBinder(bean);
Map<String, String> values = new LinkedHashMap<String, String>(); Map<String, String> values = new LinkedHashMap<String, String>();
values.put("string", "test"); values.put("string", "test");
// bad value // bad value
@ -91,7 +101,6 @@ public class GenericBinderTests {
@Test @Test
public void bindSingleValuePropertyFormatter() throws ParseException { public void bindSingleValuePropertyFormatter() throws ParseException {
GenericBinder binder = new GenericBinder(bean);
binder.bindingRule("date").formatWith(new DateFormatter()); binder.bindingRule("date").formatWith(new DateFormatter());
binder.bind(Collections.singletonMap("date", "2009-06-01")); binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate()); assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
@ -99,7 +108,6 @@ public class GenericBinderTests {
@Test @Test
public void bindSingleValuePropertyFormatterParseException() { public void bindSingleValuePropertyFormatterParseException() {
GenericBinder binder = new GenericBinder(bean);
binder.bindingRule("date").formatWith(new DateFormatter()); binder.bindingRule("date").formatWith(new DateFormatter());
BindingResults results = binder.bind(Collections.singletonMap("date", "bogus")); BindingResults results = binder.bind(Collections.singletonMap("date", "bogus"));
assertEquals(1, results.size()); assertEquals(1, results.size());
@ -109,8 +117,6 @@ public class GenericBinderTests {
@Test @Test
public void bindSingleValueWithFormatterRegistedByType() throws ParseException { public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
GenericBinder binder = new GenericBinder(bean);
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry(); GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
formatterRegistry.add(Date.class, new DateFormatter()); formatterRegistry.add(Date.class, new DateFormatter());
binder.setFormatterRegistry(formatterRegistry); binder.setFormatterRegistry(formatterRegistry);
@ -119,40 +125,46 @@ public class GenericBinderTests {
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate()); assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
} }
/*
@Test @Test
public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException { public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException {
binder.addBinding("currency"); GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
binder.registerFormatterFactory(new CurrencyAnnotationFormatterFactory()); formatterRegistry.add(new CurrencyAnnotationFormatterFactory());
binder.setFormatterRegistry(formatterRegistry);
binder.bind(Collections.singletonMap("currency", "$23.56")); binder.bind(Collections.singletonMap("currency", "$23.56"));
assertEquals(new BigDecimal("23.56"), bean.getCurrency()); assertEquals(new BigDecimal("23.56"), bean.getCurrency());
} }
@Test(expected = NoSuchBindingException.class) @Test
public void bindSingleValuePropertyNotFound() throws ParseException { public void bindSingleValuePropertyNotFound() throws ParseException {
binder.bind(Collections.singletonMap("bogus", "2009-06-01")); BindingResults results = binder.bind(Collections.singletonMap("bogus", "2009-06-01"));
assertEquals("bogus", results.get(0).getProperty());
assertTrue(results.get(0).isFailure());
assertEquals("propertyNotFound", results.get(0).getAlert().getCode());
} }
@Test(expected=MissingSourceValuesException.class) @Test(expected=MissingSourceValuesException.class)
public void bindMissingRequiredSourceValue() { public void bindMissingRequiredSourceValue() {
binder.addBinding("string"); binder.setRequired(new String[] {
binder.addBinding("integer").required(); "integer"
Map<String, String> userMap = new LinkedHashMap<String, String>(); });
userMap.put("string", "test"); // missing "integer" - violated bind contract
// missing "integer" binder.bind(Collections.singletonMap("string", "test"));
binder.bind(userMap);
} }
@Test @Test
public void getBindingCustomFormatter() { public void getBindingCustomFormatter() {
binder.addBinding("currency").formatWith(new CurrencyFormatter());
Binding b = binder.getBinding("currency"); Binding b = binder.getBinding("currency");
assertFalse(b.isIndexable()); assertFalse(b.isList());
assertFalse(b.isMap());
assertEquals("", b.getValue()); assertEquals("", b.getValue());
b.setValue("$23.56"); b.applySourceValue("$23.56");
assertEquals("$23.56", b.getValue());
b.commit();
assertEquals("$23.56", b.getValue()); assertEquals("$23.56", b.getValue());
} }
/*
@Test @Test
public void getBindingCustomFormatterRequiringTypeCoersion() { public void getBindingCustomFormatterRequiringTypeCoersion() {
// IntegerFormatter formats Longs, so conversion from Integer -> Long is performed // IntegerFormatter formats Longs, so conversion from Integer -> Long is performed

Loading…
Cancel
Save