numerous binding enhancements; removed lazy binding for time being
This commit is contained in:
parent
5ff6191d72
commit
2bbf827d57
|
|
@ -23,25 +23,14 @@ import java.util.Map;
|
|||
* @since 3.0
|
||||
* @see #bind(Map)
|
||||
*/
|
||||
public interface Binder {
|
||||
|
||||
/**
|
||||
* The model object this binder binds to.
|
||||
* @return the model object
|
||||
*/
|
||||
Object getModel();
|
||||
|
||||
/**
|
||||
* Returns the binding for the property.
|
||||
* @param property the property path
|
||||
* @return the binding
|
||||
*/
|
||||
Binding getBinding(String property);
|
||||
public interface Binder extends BindingFactory {
|
||||
|
||||
/**
|
||||
* Bind the source values to the properties of the model.
|
||||
* A result is returned for each registered {@link Binding}.
|
||||
* @param sourceValues the source values to bind
|
||||
* @return the results of the binding operation
|
||||
* @throws MissingSourceValuesException when the sourceValues Map is missing entries for required bindings
|
||||
*/
|
||||
BindingResults bind(Map<String, ? extends Object> sourceValues);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,14 +23,21 @@ package org.springframework.ui.binding;
|
|||
public interface Binding {
|
||||
|
||||
/**
|
||||
* The formatted value to display in the user interface.
|
||||
* The name of the bound model property.
|
||||
*/
|
||||
String getProperty();
|
||||
|
||||
/**
|
||||
* The formatted property value to display in the user interface.
|
||||
*/
|
||||
String getValue();
|
||||
|
||||
/**
|
||||
* Set the property associated with this binding to the value provided.
|
||||
* Set the property to the value provided.
|
||||
* The value may be a formatted String, a formatted String[] if a collection binding, or an Object of a type that can be coersed to the underlying property type.
|
||||
* @param value the new value to bind
|
||||
* @return a summary of the result of the binding
|
||||
* @throws BindException if an unrecoverable exception occurs
|
||||
*/
|
||||
BindingResult setValue(Object value);
|
||||
|
||||
|
|
@ -58,5 +65,4 @@ public interface Binding {
|
|||
*/
|
||||
Class<?> getType();
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -16,16 +16,21 @@
|
|||
package org.springframework.ui.binding;
|
||||
|
||||
/**
|
||||
* A factory for model Binders.
|
||||
* A factory for model property bindings.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface BinderFactory {
|
||||
|
||||
public interface BindingFactory {
|
||||
|
||||
/**
|
||||
* Get the Binder for the model
|
||||
* @param model the model
|
||||
* @return the binder
|
||||
* The model object for which property bindings may be accessed.
|
||||
*/
|
||||
Binder getBinder(Object model);
|
||||
}
|
||||
Object getModel();
|
||||
|
||||
/**
|
||||
* Get a binding to a model property..
|
||||
* @param property the property path
|
||||
* @throws NoSuchBindingException if no binding to the property exists
|
||||
*/
|
||||
Binding getBinding(String property);
|
||||
|
||||
}
|
||||
|
|
@ -27,7 +27,8 @@ import org.springframework.ui.alert.Alert;
|
|||
public interface BindingResult {
|
||||
|
||||
/**
|
||||
* The name of the model property associated with this binding result.
|
||||
* The model property this binding result is for.
|
||||
* @see Binder#getBinding(String)
|
||||
*/
|
||||
String getProperty();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2004-2009 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.ui.binding;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Exception thrown by a Binder when a required source value is missing unexpectedly from the sourceValues map.
|
||||
* Indicates a client configuration error.
|
||||
* @author Keith Donald
|
||||
* @see Binder#bind(Map)
|
||||
*/
|
||||
public class MissingSourceValuesException extends RuntimeException {
|
||||
|
||||
private List<String> missing;
|
||||
|
||||
/**
|
||||
* Creates a new missing source values exeption.
|
||||
* @param missing
|
||||
* @param sourceValues
|
||||
*/
|
||||
public MissingSourceValuesException(List<String> missing, Map<String, ? extends Object> sourceValues) {
|
||||
super(getMessage(missing, sourceValues));
|
||||
this.missing = missing;
|
||||
}
|
||||
|
||||
/**
|
||||
* The property paths for which source values were missing.
|
||||
*/
|
||||
public List<String> getMissing() {
|
||||
return missing;
|
||||
}
|
||||
|
||||
private static String getMessage(List<String> missingRequired, Map<String, ? extends Object> sourceValues) {
|
||||
if (missingRequired.size() == 1) {
|
||||
return "Missing a source value for required propertyPath [" + missingRequired.get(0) + "]; sourceValues map contained " + sourceValues.keySet();
|
||||
} else {
|
||||
return "Missing source values to required propertyPaths " + missingRequired + "; sourceValues map contained " + sourceValues.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -31,10 +31,4 @@ public @interface Model {
|
|||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Configures strict model binding.
|
||||
* @see Binder#setStrict(boolean)
|
||||
*/
|
||||
boolean strictBinding() default false;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package org.springframework.ui.binding;
|
||||
|
||||
/**
|
||||
* Thrown by a BindingFactory when no binding to a property exists.
|
||||
* @author Keith Donald
|
||||
* @see BindingFactory#getBinding(String)
|
||||
*/
|
||||
public class NoSuchBindingException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new no such binding exception.
|
||||
* @param property the requested property for which there is no binding
|
||||
*/
|
||||
public NoSuchBindingException(String property) {
|
||||
super("No binding to property '" + property + "' exists");
|
||||
}
|
||||
}
|
||||
|
|
@ -8,33 +8,26 @@ import java.lang.reflect.Method;
|
|||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.ui.binding.Bound;
|
||||
import org.springframework.ui.binding.Model;
|
||||
|
||||
final class AnnotatedModelBinderConfigurer {
|
||||
|
||||
public void configure(GenericBinder binder) {
|
||||
Class<?> modelClass = binder.getModel().getClass();
|
||||
Model m = AnnotationUtils.findAnnotation(modelClass, Model.class);
|
||||
if (m != null) {
|
||||
binder.setStrict(m.strictBinding());
|
||||
BeanInfo beanInfo;
|
||||
try {
|
||||
beanInfo = Introspector.getBeanInfo(modelClass);
|
||||
} catch (IntrospectionException e) {
|
||||
throw new IllegalStateException("Unable to introspect model " + binder.getModel(), e);
|
||||
}
|
||||
if (binder.isStrict()) {
|
||||
BeanInfo beanInfo;
|
||||
try {
|
||||
beanInfo = Introspector.getBeanInfo(modelClass);
|
||||
} catch (IntrospectionException e) {
|
||||
throw new IllegalStateException("Unable to introspect model " + binder.getModel(), e);
|
||||
}
|
||||
// TODO do we have to still flush introspector cache here?
|
||||
for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
|
||||
Method getter = prop.getReadMethod();
|
||||
Bound b = AnnotationUtils.getAnnotation(getter, Bound.class);
|
||||
if (b != null) {
|
||||
// TODO should we wire formatter here if using a format annotation - an optimization?
|
||||
binder.configureBinding(new BindingConfiguration(prop.getName(), null));
|
||||
}
|
||||
// TODO do we have to still flush introspector cache here?
|
||||
for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
|
||||
Method getter = prop.getReadMethod();
|
||||
Bound b = AnnotationUtils.getAnnotation(getter, Bound.class);
|
||||
if (b != null) {
|
||||
// TODO should we wire formatter here if using a format annotation - an optimization?
|
||||
binder.addBinding(prop.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,43 +15,33 @@
|
|||
*/
|
||||
package org.springframework.ui.binding.support;
|
||||
|
||||
import org.springframework.ui.binding.Binding;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* Configuration used to create a new {@link Binding} registered with a {@link GenericBinder}.
|
||||
* A fluent interface for configuring a newly added binding to a property path.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see GenericBinder#configureBinding(BindingConfiguration)
|
||||
* @see GenericBinder#addBinding(String)
|
||||
*/
|
||||
public final class BindingConfiguration {
|
||||
|
||||
private String property;
|
||||
|
||||
private Formatter<?> formatter;
|
||||
public interface BindingConfiguration {
|
||||
|
||||
/**
|
||||
* Creates a new Binding configuration.
|
||||
* @param property the property to bind to
|
||||
* @param formatter the formatter to use to format property values
|
||||
* Set the Formatter to use to format bound property values.
|
||||
* If a collection property, the formatter is used to format collection element values.
|
||||
* Default is null.
|
||||
*/
|
||||
public BindingConfiguration(String property, Formatter<?> formatter) {
|
||||
this.property = property;
|
||||
this.formatter = formatter;
|
||||
}
|
||||
BindingConfiguration formatWith(Formatter<?> formatter);
|
||||
|
||||
/**
|
||||
* The name of the model property to bind to.
|
||||
* Mark the binding as required.
|
||||
* A required binding will generate an exception if no sourceValue is provided to a bind invocation.
|
||||
* This attribute is used to detect client configuration errors.
|
||||
* It is not intended to be used as a user data validation constraint.
|
||||
* Examples:
|
||||
* <pre>
|
||||
* name=required - will generate an exception if 'name' is not contained in the source values map.
|
||||
* addresses=required - will generate an exception if 'addresses[n]' is not contained in the source value map for at least one n.
|
||||
* addresses.city=required - will generate an exception if 'addresses[n].city' is not present in the source values map, for every address[n].
|
||||
* </pre>
|
||||
*/
|
||||
public String getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Formatter to use to format bound property values.
|
||||
*/
|
||||
public Formatter<?> getFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
}
|
||||
BindingConfiguration required();
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2004-2009 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.ui.binding.support;
|
||||
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
final class DefaultBindingConfiguration implements BindingConfiguration {
|
||||
|
||||
private String propertyPath;
|
||||
|
||||
private Formatter<?> formatter;
|
||||
|
||||
private boolean required;
|
||||
|
||||
/**
|
||||
* Creates a new Binding configuration.
|
||||
* @param property the property to bind to
|
||||
* @param formatter the formatter to use to format property values
|
||||
*/
|
||||
public DefaultBindingConfiguration(String propertyPath) {
|
||||
this.propertyPath = propertyPath;
|
||||
}
|
||||
|
||||
// implementing BindingConfiguration
|
||||
|
||||
public BindingConfiguration formatWith(Formatter<?> formatter) {
|
||||
this.formatter = formatter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BindingConfiguration required() {
|
||||
this.required = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPropertyPath() {
|
||||
return propertyPath;
|
||||
}
|
||||
|
||||
public Formatter<?> getFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,7 +26,7 @@ import java.util.Map;
|
|||
* @see #setDefaultPrefix(String)
|
||||
* @see #setPresentPrefix(String)
|
||||
*/
|
||||
class WebBinder extends GenericBinder {
|
||||
public class WebBinder extends GenericBinder {
|
||||
|
||||
private String defaultPrefix = "!";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright 2004-2009 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.ui.binding.support;
|
||||
|
||||
import org.springframework.ui.binding.Binder;
|
||||
import org.springframework.ui.binding.BinderFactory;
|
||||
|
||||
/**
|
||||
* Factory for Binders suited for use in web environments.
|
||||
* TODO - BinderConfiguration objects indexed by model class?
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public class WebBinderFactory implements BinderFactory {
|
||||
|
||||
public Binder getBinder(Object model) {
|
||||
WebBinder binder = new WebBinder(model);
|
||||
new AnnotatedModelBinderConfigurer().configure(binder);
|
||||
return binder;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,12 +15,15 @@
|
|||
*/
|
||||
package org.springframework.ui.lifecycle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ui.alert.AlertContext;
|
||||
import org.springframework.ui.binding.Binder;
|
||||
import org.springframework.ui.binding.BindingResult;
|
||||
import org.springframework.ui.binding.BindingResults;
|
||||
import org.springframework.ui.validation.ValidationFailure;
|
||||
import org.springframework.ui.validation.Validator;
|
||||
|
||||
/**
|
||||
|
|
@ -58,17 +61,28 @@ public final class BindAndValidateLifecycle {
|
|||
this.validationDecider = validationDecider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the bind and validate lifecycle.
|
||||
* Any bind or validation errors are recorded as alerts against the {@link AlertContext}.
|
||||
* @param sourceValues the source values to bind and validate
|
||||
*/
|
||||
public void execute(Map<String, ? extends Object> sourceValues) {
|
||||
BindingResults bindingResults = binder.bind(sourceValues);
|
||||
if (validator != null && validationDecider.shouldValidateAfter(bindingResults)) {
|
||||
// TODO get validation results
|
||||
validator.validate(binder.getModel(), bindingResults.successes().properties());
|
||||
}
|
||||
List<ValidationFailure> validationFailures = validate(bindingResults);
|
||||
for (BindingResult result : bindingResults.failures()) {
|
||||
// TODO - you may want to ignore some alerts like propertyNotFound
|
||||
alertContext.add(result.getProperty(), result.getAlert());
|
||||
}
|
||||
// TODO translate validation results into messages
|
||||
for (ValidationFailure failure : validationFailures) {
|
||||
alertContext.add(failure.getProperty(), failure.getAlert());
|
||||
}
|
||||
}
|
||||
|
||||
private List<ValidationFailure> validate(BindingResults bindingResults) {
|
||||
if (validator != null && validationDecider.shouldValidateAfter(bindingResults)) {
|
||||
return validator.validate(binder.getModel(), bindingResults.successes().properties());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package org.springframework.ui.validation;
|
||||
|
||||
import org.springframework.ui.alert.Alert;
|
||||
|
||||
/**
|
||||
* A single validation failure generated by a Validator.
|
||||
* @author Keith Donald
|
||||
* @see Validator#validate(Object, java.util.List)
|
||||
*/
|
||||
public interface ValidationFailure {
|
||||
|
||||
/**
|
||||
* The name of the model property associated with this validation result.
|
||||
*/
|
||||
String getProperty();
|
||||
|
||||
/**
|
||||
* Gets the alert for this validation failure, appropriate for rendering the failure to the user.
|
||||
*/
|
||||
Alert getAlert();
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package org.springframework.ui.validation;
|
||||
|
||||
import org.springframework.ui.alert.Alert;
|
||||
|
||||
public interface ValidationResult {
|
||||
|
||||
/**
|
||||
* The name of the model property associated with this validation result.
|
||||
*/
|
||||
String getProperty();
|
||||
|
||||
/**
|
||||
* Indicates if the validation failed.
|
||||
*/
|
||||
boolean isFailure();
|
||||
|
||||
/**
|
||||
* Gets the alert for this validation result, appropriate for rendering the result to the user.
|
||||
* An alert describing a successful validation will have info severity.
|
||||
* An alert describing a failed validation will have either warning or error severity.
|
||||
*/
|
||||
Alert getAlert();
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2009 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.ui.validation;
|
||||
|
||||
public interface ValidationResults extends Iterable<ValidationResult> {
|
||||
|
||||
}
|
||||
|
|
@ -17,6 +17,18 @@ package org.springframework.ui.validation;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public interface Validator<M> {
|
||||
ValidationResults validate(M model, List<String> properties);
|
||||
/**
|
||||
* Validates a model object.
|
||||
* @author Keith Donald
|
||||
* @param <M> the type of model object this validator supports
|
||||
*/
|
||||
public interface Validator {
|
||||
|
||||
/**
|
||||
* Validate the properties of the model object.
|
||||
* @param model the model object
|
||||
* @param properties the properties to validate
|
||||
* @return a list of validation failures, empty if there were no failures
|
||||
*/
|
||||
List<ValidationFailure> validate(Object model, List<String> properties);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue