property not found / required
This commit is contained in:
parent
3cd3cddbe0
commit
f39f91701d
|
|
@ -19,18 +19,20 @@ import java.beans.BeanInfo;
|
|||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.convert.TypeConverter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultTypeConverter;
|
||||
import org.springframework.ui.binding.Binder;
|
||||
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.Binding.BindingStatus;
|
||||
import org.springframework.ui.binding.config.BindingRuleConfiguration;
|
||||
import org.springframework.ui.binding.config.Condition;
|
||||
|
|
@ -57,6 +59,8 @@ public class GenericBinder implements Binder {
|
|||
|
||||
private MessageSource messageSource;
|
||||
|
||||
private String[] requiredProperties = new String[0];
|
||||
|
||||
/**
|
||||
* Creates a new binder for the model object.
|
||||
* @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) {
|
||||
sourceValues = filter(sourceValues);
|
||||
checkRequired(sourceValues);
|
||||
ArrayListBindingResults results = new ArrayListBindingResults(sourceValues.size());
|
||||
for (Map.Entry<String, ? extends Object> sourceValue : sourceValues.entrySet()) {
|
||||
Binding binding = getBinding(sourceValue.getKey());
|
||||
results.add(bind(sourceValue, binding));
|
||||
try {
|
||||
Binding binding = getBinding(sourceValue.getKey());
|
||||
results.add(bind(sourceValue, binding));
|
||||
} catch (PropertyNotFoundException e) {
|
||||
results.add(new PropertyNotFoundResult(sourceValue.getKey(), sourceValue.getValue(), messageSource));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
|
@ -163,6 +172,24 @@ public class GenericBinder implements Binder {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
GenericBindingRule rule = bindingRules.get(property);
|
||||
|
|
@ -217,6 +244,10 @@ public class GenericBinder implements Binder {
|
|||
|
||||
// implementing BindingContext
|
||||
|
||||
public MessageSource getMessageSource() {
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
public TypeConverter getTypeConverter() {
|
||||
return typeConverter;
|
||||
}
|
||||
|
|
@ -326,8 +357,7 @@ public class GenericBinder implements Binder {
|
|||
return propDesc;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No property '" + property + "' found on model ["
|
||||
+ modelClass.getName() + "]");
|
||||
throw new PropertyNotFoundException(property, modelClass);
|
||||
}
|
||||
|
||||
private BeanInfo getBeanInfo(Class<?> clazz) {
|
||||
|
|
@ -341,6 +371,8 @@ public class GenericBinder implements Binder {
|
|||
|
||||
public interface BindingContext {
|
||||
|
||||
MessageSource getMessageSource();
|
||||
|
||||
TypeConverter getTypeConverter();
|
||||
|
||||
Condition getEditableCondition();
|
||||
|
|
@ -362,4 +394,8 @@ public class GenericBinder implements Binder {
|
|||
|
||||
}
|
||||
|
||||
public void setRequired(String[] propertyPaths) {
|
||||
this.requiredProperties = propertyPaths;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
|
|||
import java.math.BigDecimal;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
|
|
@ -16,23 +17,33 @@ import java.util.Map;
|
|||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
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.format.AnnotationFormatterFactory;
|
||||
import org.springframework.ui.format.Formatted;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.ui.format.date.DateFormatter;
|
||||
import org.springframework.ui.format.number.CurrencyFormat;
|
||||
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 {
|
||||
|
||||
private GenericBinder binder;
|
||||
|
||||
private TestBean bean;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
bean = new TestBean();
|
||||
binder = new GenericBinder(bean);
|
||||
LocaleContextHolder.setLocale(Locale.US);
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +88,6 @@ public class GenericBinderTests {
|
|||
|
||||
@Test
|
||||
public void bindSingleValuesWithDefaultTypeConversionFailure() {
|
||||
GenericBinder binder = new GenericBinder(bean);
|
||||
Map<String, String> values = new LinkedHashMap<String, String>();
|
||||
values.put("string", "test");
|
||||
// bad value
|
||||
|
|
@ -91,7 +101,6 @@ public class GenericBinderTests {
|
|||
|
||||
@Test
|
||||
public void bindSingleValuePropertyFormatter() throws ParseException {
|
||||
GenericBinder binder = new GenericBinder(bean);
|
||||
binder.bindingRule("date").formatWith(new DateFormatter());
|
||||
binder.bind(Collections.singletonMap("date", "2009-06-01"));
|
||||
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
|
||||
|
|
@ -99,7 +108,6 @@ public class GenericBinderTests {
|
|||
|
||||
@Test
|
||||
public void bindSingleValuePropertyFormatterParseException() {
|
||||
GenericBinder binder = new GenericBinder(bean);
|
||||
binder.bindingRule("date").formatWith(new DateFormatter());
|
||||
BindingResults results = binder.bind(Collections.singletonMap("date", "bogus"));
|
||||
assertEquals(1, results.size());
|
||||
|
|
@ -109,8 +117,6 @@ public class GenericBinderTests {
|
|||
|
||||
@Test
|
||||
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
|
||||
GenericBinder binder = new GenericBinder(bean);
|
||||
|
||||
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
|
||||
formatterRegistry.add(Date.class, new DateFormatter());
|
||||
binder.setFormatterRegistry(formatterRegistry);
|
||||
|
|
@ -119,40 +125,46 @@ public class GenericBinderTests {
|
|||
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException {
|
||||
binder.addBinding("currency");
|
||||
binder.registerFormatterFactory(new CurrencyAnnotationFormatterFactory());
|
||||
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
|
||||
formatterRegistry.add(new CurrencyAnnotationFormatterFactory());
|
||||
binder.setFormatterRegistry(formatterRegistry);
|
||||
|
||||
binder.bind(Collections.singletonMap("currency", "$23.56"));
|
||||
assertEquals(new BigDecimal("23.56"), bean.getCurrency());
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchBindingException.class)
|
||||
@Test
|
||||
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)
|
||||
public void bindMissingRequiredSourceValue() {
|
||||
binder.addBinding("string");
|
||||
binder.addBinding("integer").required();
|
||||
Map<String, String> userMap = new LinkedHashMap<String, String>();
|
||||
userMap.put("string", "test");
|
||||
// missing "integer"
|
||||
binder.bind(userMap);
|
||||
binder.setRequired(new String[] {
|
||||
"integer"
|
||||
});
|
||||
// missing "integer" - violated bind contract
|
||||
binder.bind(Collections.singletonMap("string", "test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBindingCustomFormatter() {
|
||||
binder.addBinding("currency").formatWith(new CurrencyFormatter());
|
||||
Binding b = binder.getBinding("currency");
|
||||
assertFalse(b.isIndexable());
|
||||
assertFalse(b.isList());
|
||||
assertFalse(b.isMap());
|
||||
assertEquals("", b.getValue());
|
||||
b.setValue("$23.56");
|
||||
b.applySourceValue("$23.56");
|
||||
assertEquals("$23.56", b.getValue());
|
||||
b.commit();
|
||||
assertEquals("$23.56", b.getValue());
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void getBindingCustomFormatterRequiringTypeCoersion() {
|
||||
// IntegerFormatter formats Longs, so conversion from Integer -> Long is performed
|
||||
|
|
|
|||
Loading…
Reference in New Issue