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
|
* @since 3.0
|
||||||
* @see #bind(Map)
|
* @see #bind(Map)
|
||||||
*/
|
*/
|
||||||
public interface Binder {
|
public interface Binder extends BindingFactory {
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind the source values to the properties of the model.
|
* 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
|
* @param sourceValues the source values to bind
|
||||||
* @return the results of the binding operation
|
* @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);
|
BindingResults bind(Map<String, ? extends Object> sourceValues);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,21 @@ package org.springframework.ui.binding;
|
||||||
public interface 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();
|
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.
|
* 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
|
* @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);
|
BindingResult setValue(Object value);
|
||||||
|
|
||||||
|
|
@ -58,5 +65,4 @@ public interface Binding {
|
||||||
*/
|
*/
|
||||||
Class<?> getType();
|
Class<?> getType();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -16,16 +16,21 @@
|
||||||
package org.springframework.ui.binding;
|
package org.springframework.ui.binding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for model Binders.
|
* A factory for model property bindings.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @since 3.0
|
|
||||||
*/
|
*/
|
||||||
public interface BinderFactory {
|
public interface BindingFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Binder for the model
|
* The model object for which property bindings may be accessed.
|
||||||
* @param model the model
|
|
||||||
* @return the binder
|
|
||||||
*/
|
*/
|
||||||
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 {
|
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();
|
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 "";
|
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.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.ui.binding.Bound;
|
import org.springframework.ui.binding.Bound;
|
||||||
import org.springframework.ui.binding.Model;
|
|
||||||
|
|
||||||
final class AnnotatedModelBinderConfigurer {
|
final class AnnotatedModelBinderConfigurer {
|
||||||
|
|
||||||
public void configure(GenericBinder binder) {
|
public void configure(GenericBinder binder) {
|
||||||
Class<?> modelClass = binder.getModel().getClass();
|
Class<?> modelClass = binder.getModel().getClass();
|
||||||
Model m = AnnotationUtils.findAnnotation(modelClass, Model.class);
|
BeanInfo beanInfo;
|
||||||
if (m != null) {
|
try {
|
||||||
binder.setStrict(m.strictBinding());
|
beanInfo = Introspector.getBeanInfo(modelClass);
|
||||||
|
} catch (IntrospectionException e) {
|
||||||
|
throw new IllegalStateException("Unable to introspect model " + binder.getModel(), e);
|
||||||
}
|
}
|
||||||
if (binder.isStrict()) {
|
// TODO do we have to still flush introspector cache here?
|
||||||
BeanInfo beanInfo;
|
for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
|
||||||
try {
|
Method getter = prop.getReadMethod();
|
||||||
beanInfo = Introspector.getBeanInfo(modelClass);
|
Bound b = AnnotationUtils.getAnnotation(getter, Bound.class);
|
||||||
} catch (IntrospectionException e) {
|
if (b != null) {
|
||||||
throw new IllegalStateException("Unable to introspect model " + binder.getModel(), e);
|
// TODO should we wire formatter here if using a format annotation - an optimization?
|
||||||
}
|
binder.addBinding(prop.getName());
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,43 +15,33 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.ui.binding.support;
|
package org.springframework.ui.binding.support;
|
||||||
|
|
||||||
import org.springframework.ui.binding.Binding;
|
|
||||||
import org.springframework.ui.format.Formatter;
|
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
|
* @author Keith Donald
|
||||||
* @since 3.0
|
* @see GenericBinder#addBinding(String)
|
||||||
* @see GenericBinder#configureBinding(BindingConfiguration)
|
|
||||||
*/
|
*/
|
||||||
public final class BindingConfiguration {
|
public interface BindingConfiguration {
|
||||||
|
|
||||||
private String property;
|
|
||||||
|
|
||||||
private Formatter<?> formatter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Binding configuration.
|
* Set the Formatter to use to format bound property values.
|
||||||
* @param property the property to bind to
|
* If a collection property, the formatter is used to format collection element values.
|
||||||
* @param formatter the formatter to use to format property values
|
* Default is null.
|
||||||
*/
|
*/
|
||||||
public BindingConfiguration(String property, Formatter<?> formatter) {
|
BindingConfiguration formatWith(Formatter<?> formatter);
|
||||||
this.property = property;
|
|
||||||
this.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() {
|
BindingConfiguration required();
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Formatter to use to format bound property values.
|
|
||||||
*/
|
|
||||||
public Formatter<?> getFormatter() {
|
|
||||||
return formatter;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -22,11 +22,12 @@ import java.lang.reflect.TypeVariable;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.expression.MapAccessor;
|
import org.springframework.context.expression.MapAccessor;
|
||||||
|
|
@ -55,6 +56,8 @@ 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.NoSuchBindingException;
|
||||||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||||
import org.springframework.ui.format.Formatter;
|
import org.springframework.ui.format.Formatter;
|
||||||
import org.springframework.ui.message.MessageBuilder;
|
import org.springframework.ui.message.MessageBuilder;
|
||||||
|
|
@ -65,10 +68,11 @@ import org.springframework.util.Assert;
|
||||||
* A generic {@link Binder binder} suitable for use in most environments.
|
* A generic {@link Binder binder} suitable for use in most environments.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
* @see #configureBinding(BindingConfiguration)
|
* @see #addBinding(String)
|
||||||
|
* @see #registerFormatter(Class, Formatter)
|
||||||
|
* @see #registerFormatterFactory(AnnotationFormatterFactory)
|
||||||
* @see #setFormatterRegistry(FormatterRegistry)
|
* @see #setFormatterRegistry(FormatterRegistry)
|
||||||
* @see #setMessageSource(MessageSource)
|
* @see #setMessageSource(MessageSource)
|
||||||
* @see #setStrict(boolean)
|
|
||||||
* @see #setTypeConverter(TypeConverter)
|
* @see #setTypeConverter(TypeConverter)
|
||||||
* @see #bind(Map)
|
* @see #bind(Map)
|
||||||
*/
|
*/
|
||||||
|
|
@ -79,7 +83,7 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
private Object model;
|
private Object model;
|
||||||
|
|
||||||
private Map<String, Binding> bindings;
|
private Set<BindingFactory> bindingFactories;
|
||||||
|
|
||||||
private FormatterRegistry formatterRegistry = new GenericFormatterRegistry();
|
private FormatterRegistry formatterRegistry = new GenericFormatterRegistry();
|
||||||
|
|
||||||
|
|
@ -87,8 +91,6 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
private TypeConverter typeConverter;
|
private TypeConverter typeConverter;
|
||||||
|
|
||||||
private boolean strict = false;
|
|
||||||
|
|
||||||
private static Formatter defaultFormatter = new DefaultFormatter();
|
private static Formatter defaultFormatter = new DefaultFormatter();
|
||||||
|
|
||||||
private MessageSource messageSource;
|
private MessageSource messageSource;
|
||||||
|
|
@ -98,36 +100,57 @@ public class GenericBinder implements Binder {
|
||||||
* @param model the model object containing properties this binder will bind to
|
* @param model the model object containing properties this binder will bind to
|
||||||
*/
|
*/
|
||||||
public GenericBinder(Object model) {
|
public GenericBinder(Object model) {
|
||||||
Assert.notNull(model, "The model Object is required");
|
Assert.notNull(model, "The model to bind to is required");
|
||||||
this.model = model;
|
this.model = model;
|
||||||
bindings = new HashMap<String, Binding>();
|
bindingFactories = new LinkedHashSet<BindingFactory>();
|
||||||
int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull
|
int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull
|
||||||
| SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize;
|
| SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize;
|
||||||
expressionParser = new SpelExpressionParser(parserConfig);
|
expressionParser = new SpelExpressionParser(parserConfig);
|
||||||
typeConverter = new DefaultTypeConverter();
|
typeConverter = new DefaultTypeConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getModel() {
|
/**
|
||||||
return model;
|
* Add a binding to a model property.
|
||||||
|
* The property may be a path to a member property like "name", or a nested property like "address.city" or "addresses.city".
|
||||||
|
* If the property path is nested and traverses a collection or Map, do not use indexes.
|
||||||
|
* The property path should express the model-class property structure like addresses.city, not an object structure like addresses[0].city.
|
||||||
|
* Examples:
|
||||||
|
* <pre>
|
||||||
|
* name - bind to property 'name'
|
||||||
|
* addresses - bind to property 'addresses', presumably a List<Address> e.g. allowing property expressions like addresses={ 12345 Macy Lane, 1977 Bel Aire Estates } and addresses[0]=12345 Macy Lane
|
||||||
|
* addresses.city - bind to property 'addresses.city', for all indexed addresses in the collection e.g. allowing property expressions like addresses[0].city=Melbourne
|
||||||
|
* address.city - bind to property 'address.city'
|
||||||
|
* favoriteFoodByFoodGroup - bind to property 'favoriteFoodByFoodGroup', presumably a Map<FoodGroup, Food>; e.g. allowing favoriteFoodByFoodGroup={ DAIRY=Milk, MEAT=Steak } and favoriteFoodByFoodGroup['DAIRY']=Milk
|
||||||
|
* favoriteFoodByFoodGroup.name - bind to property 'favoriteFoodByFoodGroup.name', for all keyed Foods in the map; e.g. allowing favoriteFoodByFoodGroup['DAIRY'].name=Milk
|
||||||
|
* </pre>
|
||||||
|
* @param propertyPath the model property path
|
||||||
|
* @return a BindingConfiguration object, allowing additional configuration of the newly added binding
|
||||||
|
* @throws IllegalArgumentException if no such property path exists on the model
|
||||||
|
*/
|
||||||
|
public BindingConfiguration addBinding(String propertyPath) {
|
||||||
|
DefaultBindingConfiguration configuration = new DefaultBindingConfiguration(propertyPath);
|
||||||
|
bindingFactories.add(new BindingFactory(configuration));
|
||||||
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this binder strict?
|
* Register a Formatter to format the model properties of a specific property type.
|
||||||
* A strict binder requires all bindings to be registered explicitly using {@link #configureBinding(BindingConfiguration)}.
|
* Convenience method that calls {@link FormatterRegistry#add(Class, Formatter)} internally.
|
||||||
|
* The type may be a marker annotation type; if so, the Formatter will be used on properties having that marker annotation.
|
||||||
|
* @param propertyType the model property type
|
||||||
|
* @param formatter the formatter
|
||||||
*/
|
*/
|
||||||
public boolean isStrict() {
|
public void registerFormatter(Class<?> propertyType, Formatter<?> formatter) {
|
||||||
return strict;
|
formatterRegistry.add(propertyType, formatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures if this binder is <i>strict</i>.
|
* Register a FormatterFactory that creates Formatter instances as required to format model properties annotated with a specific annotation.
|
||||||
* A strict binder requires all bindings to be registered explicitly using {@link #configureBinding(BindingConfiguration)}.
|
* Convenience method that calls {@link FormatterRegistry#add(AnnotationFormatterFactory)} internally.
|
||||||
* An <i>optimistic</i> binder will implicitly create bindings as required to support {@link #bind(UserValues)} operations.
|
* @param factory the formatter factory
|
||||||
* Default is optimistic.
|
|
||||||
* @param strict strict binder status
|
|
||||||
*/
|
*/
|
||||||
public void setStrict(boolean strict) {
|
public void registerFormatterFactory(AnnotationFormatterFactory<?, ?> factory) {
|
||||||
this.strict = strict;
|
formatterRegistry.add(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -161,64 +184,31 @@ public class GenericBinder implements Binder {
|
||||||
this.typeConverter = typeConverter;
|
this.typeConverter = typeConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// implementing BindingFactory
|
||||||
* Configures a new binding on this binder.
|
|
||||||
* @param configuration the binding configuration
|
|
||||||
* @return the new binding created from the configuration provided
|
|
||||||
*/
|
|
||||||
public Binding configureBinding(BindingConfiguration configuration) {
|
|
||||||
Binding binding;
|
|
||||||
try {
|
|
||||||
// TODO should probably only allow binding to be created if property exists on model
|
|
||||||
binding = new BindingImpl(configuration);
|
|
||||||
} catch (org.springframework.expression.ParseException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
bindings.put(configuration.getProperty(), binding);
|
|
||||||
return binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public Object getModel() {
|
||||||
* Register a Formatter to format the model properties of a specific property type.
|
return model;
|
||||||
* Convenience method that calls {@link FormatterRegistry#add(Class, Formatter)} internally.
|
|
||||||
* The type may be a marker annotation type; if so, the Formatter will be used on properties having that marker annotation.
|
|
||||||
* @param propertyType the model property type
|
|
||||||
* @param formatter the formatter
|
|
||||||
*/
|
|
||||||
public void registerFormatter(Class<?> propertyType, Formatter<?> formatter) {
|
|
||||||
formatterRegistry.add(propertyType, formatter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a FormatterFactory that creates Formatter instances as required to format model properties annotated with a specific annotation.
|
|
||||||
* Convenience method that calls {@link FormatterRegistry#add(AnnotationFormatterFactory)} internally.
|
|
||||||
* @param factory the formatter factory
|
|
||||||
*/
|
|
||||||
public void registerFormatterFactory(AnnotationFormatterFactory<?, ?> factory) {
|
|
||||||
formatterRegistry.add(factory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Binding getBinding(String property) {
|
public Binding getBinding(String property) {
|
||||||
Binding binding = bindings.get(property);
|
for (BindingFactory factory: bindingFactories) {
|
||||||
if (binding == null && !strict) {
|
if (factory.hasBindingFor(property)) {
|
||||||
return configureBinding(new BindingConfiguration(property, null));
|
return factory.getBinding(property);
|
||||||
} else {
|
}
|
||||||
return binding;
|
|
||||||
}
|
}
|
||||||
|
throw new NoSuchBindingException(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implementing 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()) {
|
||||||
String property = sourceValue.getKey();
|
String property = sourceValue.getKey();
|
||||||
Object value = sourceValue.getValue();
|
Object value = sourceValue.getValue();
|
||||||
BindingImpl binding = (BindingImpl) getBinding(property);
|
results.add(getBinding(property).setValue(value));
|
||||||
if (binding != null) {
|
|
||||||
results.add(binding.setValue(value));
|
|
||||||
} else {
|
|
||||||
results.add(new NoSuchBindingResult(property, value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
@ -227,9 +217,9 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook subclasses may use to filter the source values to bind.
|
* Hook subclasses may use to filter the source values to bind.
|
||||||
* This hook allows the binder to pre-process the source values before binding occurs.
|
* This hook allows the binder to pre-process the source values before binding occurs.
|
||||||
- * For example, a Binder might insert empty or default values for fields that are not present.
|
* For example, a Binder might insert empty or default values for fields that are not present.
|
||||||
- * As another example, a Binder might collapse multiple source values into a single source value.
|
* As another example, a Binder might collapse multiple source values into a single source value.
|
||||||
* @param sourceValues the original source values map provided by the caller
|
* @param sourceValues the original source values map provided by the caller
|
||||||
* @return the filtered source values map that will be used to bind
|
* @return the filtered source values map that will be used to bind
|
||||||
*/
|
*/
|
||||||
|
|
@ -239,6 +229,293 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
// internal helpers
|
// internal helpers
|
||||||
|
|
||||||
|
private void checkRequired(Map<String, ? extends Object> sourceValues) {
|
||||||
|
List<String> missingRequired = new ArrayList<String>();
|
||||||
|
for (BindingFactory factory : bindingFactories) {
|
||||||
|
if (factory.configuration.isRequired()) {
|
||||||
|
boolean found = false;
|
||||||
|
for (String property : sourceValues.keySet()) {
|
||||||
|
if (factory.hasBindingFor(property)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
missingRequired.add(factory.getPropertyPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!missingRequired.isEmpty()) {
|
||||||
|
throw new MissingSourceValuesException(missingRequired, sourceValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EvaluationContext createEvaluationContext() {
|
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||||
|
context.setRootObject(model);
|
||||||
|
context.addPropertyAccessor(new MapAccessor());
|
||||||
|
context.setTypeConverter(new StandardTypeConverter(typeConverter));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BindingFactory {
|
||||||
|
|
||||||
|
private DefaultBindingConfiguration configuration;
|
||||||
|
|
||||||
|
public BindingFactory(DefaultBindingConfiguration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPropertyPath() {
|
||||||
|
return configuration.getPropertyPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBindingFor(String property) {
|
||||||
|
String propertyPath = propertyPath(property);
|
||||||
|
return configuration.getPropertyPath().equals(propertyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Binding getBinding(String property) {
|
||||||
|
Expression propertyExpression;
|
||||||
|
try {
|
||||||
|
propertyExpression = expressionParser.parseExpression(property);
|
||||||
|
} catch (org.springframework.expression.ParseException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unable to get model binding; cannot parse property expression from property string ["
|
||||||
|
+ property + "]", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
propertyExpression.getValueType(createEvaluationContext());
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
throw new IllegalArgumentException("Unable to get model binding; cannot access property '"
|
||||||
|
+ propertyExpression.getExpressionString() + "'", e);
|
||||||
|
}
|
||||||
|
return new BindingImpl(propertyExpression, configuration.getFormatter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String propertyPath(String property) {
|
||||||
|
StringBuilder sb = new StringBuilder(property);
|
||||||
|
int searchIndex = 0;
|
||||||
|
while (searchIndex != -1) {
|
||||||
|
int keyStart = sb.indexOf("[", searchIndex);
|
||||||
|
searchIndex = -1;
|
||||||
|
if (keyStart != -1) {
|
||||||
|
int keyEnd = sb.indexOf("]", keyStart + 1);
|
||||||
|
if (keyEnd != -1) {
|
||||||
|
sb.delete(keyStart, keyEnd + 1);
|
||||||
|
searchIndex = keyEnd + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class BindingImpl implements Binding {
|
||||||
|
|
||||||
|
private Expression property;
|
||||||
|
|
||||||
|
private Formatter formatter;
|
||||||
|
|
||||||
|
public BindingImpl(Expression property, Formatter formatter) {
|
||||||
|
this.property = property;
|
||||||
|
this.formatter = formatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// implementing Binding
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return property.getExpressionString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
Object value;
|
||||||
|
try {
|
||||||
|
value = property.getValue(createEvaluationContext());
|
||||||
|
} catch (ExpressionException e) {
|
||||||
|
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
||||||
|
}
|
||||||
|
return format(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindingResult setValue(Object value) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
return setStringValue((String) value);
|
||||||
|
} else if (value instanceof String[]) {
|
||||||
|
return setStringValues((String[]) value);
|
||||||
|
} else {
|
||||||
|
return setObjectValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(Object selectableValue) {
|
||||||
|
Formatter formatter = getFormatter();
|
||||||
|
Class<?> formattedType = getFormattedObjectType(formatter);
|
||||||
|
selectableValue = typeConverter.convert(selectableValue, formattedType);
|
||||||
|
return formatter.format(selectableValue, LocaleContextHolder.getLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCollection() {
|
||||||
|
Class type = getType();
|
||||||
|
TypeDescriptor<?> typeDesc = TypeDescriptor.valueOf(type);
|
||||||
|
return typeDesc.isCollection() || typeDesc.isArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getCollectionValues() {
|
||||||
|
Object multiValue;
|
||||||
|
try {
|
||||||
|
multiValue = property.getValue(createEvaluationContext());
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
||||||
|
}
|
||||||
|
if (multiValue == null) {
|
||||||
|
return EMPTY_STRING_ARRAY;
|
||||||
|
}
|
||||||
|
TypeDescriptor<?> type = TypeDescriptor.valueOf(multiValue.getClass());
|
||||||
|
String[] formattedValues;
|
||||||
|
if (type.isCollection()) {
|
||||||
|
Collection<?> values = ((Collection<?>) multiValue);
|
||||||
|
formattedValues = (String[]) Array.newInstance(String.class, values.size());
|
||||||
|
copy(values, formattedValues);
|
||||||
|
} else if (type.isArray()) {
|
||||||
|
formattedValues = (String[]) Array.newInstance(String.class, Array.getLength(multiValue));
|
||||||
|
copy((Iterable<?>) multiValue, formattedValues);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
return formattedValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public impl only
|
||||||
|
|
||||||
|
public Class<?> getType() {
|
||||||
|
Class<?> type;
|
||||||
|
try {
|
||||||
|
type = property.getValueType(createEvaluationContext());
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Failed to get property expression value type - this should not happen", e);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
|
||||||
|
private BindingResult setStringValue(String formatted) {
|
||||||
|
Object parsed;
|
||||||
|
try {
|
||||||
|
parsed = getFormatter().parse(formatted, LocaleContextHolder.getLocale());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return new InvalidFormat(property.getExpressionString(), formatted, e);
|
||||||
|
}
|
||||||
|
return setValue(parsed, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BindingResult setStringValues(String[] formatted) {
|
||||||
|
Formatter formatter = getFormatter();
|
||||||
|
Class parsedType = getFormattedObjectType(formatter);
|
||||||
|
if (parsedType == null) {
|
||||||
|
parsedType = String.class;
|
||||||
|
}
|
||||||
|
Object parsed = Array.newInstance(parsedType, formatted.length);
|
||||||
|
for (int i = 0; i < formatted.length; i++) {
|
||||||
|
Object parsedValue;
|
||||||
|
try {
|
||||||
|
parsedValue = formatter.parse(formatted[i], LocaleContextHolder.getLocale());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return new InvalidFormat(property.getExpressionString(), formatted, e);
|
||||||
|
}
|
||||||
|
Array.set(parsed, i, parsedValue);
|
||||||
|
}
|
||||||
|
return setValue(parsed, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BindingResult setObjectValue(Object value) {
|
||||||
|
return setValue(value, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Formatter getFormatter() {
|
||||||
|
if (formatter != null) {
|
||||||
|
return formatter;
|
||||||
|
} else {
|
||||||
|
TypeDescriptor type;
|
||||||
|
try {
|
||||||
|
type = property.getValueTypeDescriptor(createEvaluationContext());
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Failed to get property expression value type descriptor - this should not happen", e);
|
||||||
|
}
|
||||||
|
Formatter formatter = formatterRegistry.getFormatter(type);
|
||||||
|
return formatter != null ? formatter : defaultFormatter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(Iterable<?> values, String[] formattedValues) {
|
||||||
|
int i = 0;
|
||||||
|
for (Object value : values) {
|
||||||
|
formattedValues[i] = format(value);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BindingResult setValue(Object parsedValue, Object userValue) {
|
||||||
|
try {
|
||||||
|
property.setValue(createEvaluationContext(), parsedValue);
|
||||||
|
return new Success(property.getExpressionString(), userValue);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return new EvaluationError(property.getExpressionString(), userValue, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class getFormattedObjectType(Formatter formatter) {
|
||||||
|
// TODO consider caching this info
|
||||||
|
Class classToIntrospect = formatter.getClass();
|
||||||
|
while (classToIntrospect != null) {
|
||||||
|
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
|
||||||
|
for (Type genericInterface : genericInterfaces) {
|
||||||
|
if (genericInterface instanceof ParameterizedType) {
|
||||||
|
ParameterizedType pInterface = (ParameterizedType) genericInterface;
|
||||||
|
if (Formatter.class.isAssignableFrom((Class) pInterface.getRawType())) {
|
||||||
|
return getParameterClass(pInterface.getActualTypeArguments()[0], formatter.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classToIntrospect = classToIntrospect.getSuperclass();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class getParameterClass(Type parameterType, Class converterClass) {
|
||||||
|
if (parameterType instanceof TypeVariable) {
|
||||||
|
parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass);
|
||||||
|
}
|
||||||
|
if (parameterType instanceof Class) {
|
||||||
|
return (Class) parameterType;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType
|
||||||
|
+ "] on Formatter [" + converterClass.getName() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DefaultFormatter implements Formatter {
|
||||||
|
public String format(Object object, Locale locale) {
|
||||||
|
if (object == null) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object parse(String formatted, Locale locale) throws ParseException {
|
||||||
|
if (formatted == "") {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class ArrayListBindingResults implements BindingResults {
|
static class ArrayListBindingResults implements BindingResults {
|
||||||
|
|
||||||
private List<BindingResult> results;
|
private List<BindingResult> results;
|
||||||
|
|
@ -301,277 +578,7 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BindingImpl implements Binding {
|
class InvalidFormat implements BindingResult {
|
||||||
|
|
||||||
private Expression property;
|
|
||||||
|
|
||||||
private Formatter formatter;
|
|
||||||
|
|
||||||
public BindingImpl(BindingConfiguration config) throws org.springframework.expression.ParseException {
|
|
||||||
property = expressionParser.parseExpression(config.getProperty());
|
|
||||||
formatter = config.getFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// implementing Binding
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
Object value;
|
|
||||||
try {
|
|
||||||
value = property.getValue(createEvaluationContext());
|
|
||||||
} catch (ExpressionException e) {
|
|
||||||
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
|
||||||
}
|
|
||||||
return format(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BindingResult setValue(Object value) {
|
|
||||||
if (value instanceof String) {
|
|
||||||
return setStringValue((String) value);
|
|
||||||
} else if (value instanceof String[]) {
|
|
||||||
return setStringValues((String[]) value);
|
|
||||||
} else {
|
|
||||||
return setObjectValue(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String format(Object selectableValue) {
|
|
||||||
Formatter formatter;
|
|
||||||
try {
|
|
||||||
formatter = getFormatter();
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Failed to get property expression value type - this should not happen", e);
|
|
||||||
}
|
|
||||||
Class<?> formattedType = getFormattedObjectType(formatter);
|
|
||||||
selectableValue = typeConverter.convert(selectableValue, formattedType);
|
|
||||||
return formatter.format(selectableValue, LocaleContextHolder.getLocale());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCollection() {
|
|
||||||
Class type = getType();
|
|
||||||
TypeDescriptor<?> typeDesc = TypeDescriptor.valueOf(type);
|
|
||||||
return typeDesc.isCollection() || typeDesc.isArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getCollectionValues() {
|
|
||||||
Object multiValue;
|
|
||||||
try {
|
|
||||||
multiValue = property.getValue(createEvaluationContext());
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
throw new IllegalStateException("Failed to get property expression value - this should not happen", e);
|
|
||||||
}
|
|
||||||
if (multiValue == null) {
|
|
||||||
return EMPTY_STRING_ARRAY;
|
|
||||||
}
|
|
||||||
TypeDescriptor<?> type = TypeDescriptor.valueOf(multiValue.getClass());
|
|
||||||
String[] formattedValues;
|
|
||||||
if (type.isCollection()) {
|
|
||||||
Collection<?> values = ((Collection<?>) multiValue);
|
|
||||||
formattedValues = (String[]) Array.newInstance(String.class, values.size());
|
|
||||||
copy(values, formattedValues);
|
|
||||||
} else if (type.isArray()) {
|
|
||||||
formattedValues = (String[]) Array.newInstance(String.class, Array.getLength(multiValue));
|
|
||||||
copy((Iterable<?>) multiValue, formattedValues);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
return formattedValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
// public impl only
|
|
||||||
|
|
||||||
public Class<?> getType() {
|
|
||||||
Class<?> type;
|
|
||||||
try {
|
|
||||||
type = property.getValueType(createEvaluationContext());
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Failed to get property expression value type - this should not happen", e);
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal helpers
|
|
||||||
|
|
||||||
private BindingResult setStringValue(String formatted) {
|
|
||||||
Formatter formatter;
|
|
||||||
try {
|
|
||||||
formatter = getFormatter();
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
// could occur if the property was not found or is not readable
|
|
||||||
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
|
||||||
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e);
|
|
||||||
}
|
|
||||||
Object parsed;
|
|
||||||
try {
|
|
||||||
parsed = formatter.parse(formatted, LocaleContextHolder.getLocale());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
return new InvalidFormatResult(property.getExpressionString(), formatted, e);
|
|
||||||
}
|
|
||||||
return setValue(parsed, formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BindingResult setStringValues(String[] formatted) {
|
|
||||||
Formatter formatter;
|
|
||||||
try {
|
|
||||||
formatter = getFormatter();
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
// could occur if the property was not found or is not readable
|
|
||||||
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
|
||||||
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e);
|
|
||||||
}
|
|
||||||
Class parsedType = getFormattedObjectType(formatter);
|
|
||||||
if (parsedType == null) {
|
|
||||||
parsedType = String.class;
|
|
||||||
}
|
|
||||||
Object parsed = Array.newInstance(parsedType, formatted.length);
|
|
||||||
for (int i = 0; i < formatted.length; i++) {
|
|
||||||
Object parsedValue;
|
|
||||||
try {
|
|
||||||
parsedValue = formatter.parse(formatted[i], LocaleContextHolder.getLocale());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
return new InvalidFormatResult(property.getExpressionString(), formatted, e);
|
|
||||||
}
|
|
||||||
Array.set(parsed, i, parsedValue);
|
|
||||||
}
|
|
||||||
return setValue(parsed, formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BindingResult setObjectValue(Object value) {
|
|
||||||
return setValue(value, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Formatter getFormatter() throws EvaluationException {
|
|
||||||
if (formatter != null) {
|
|
||||||
return formatter;
|
|
||||||
} else {
|
|
||||||
Formatter<?> formatter = formatterRegistry.getFormatter(property
|
|
||||||
.getValueTypeDescriptor(createEvaluationContext()));
|
|
||||||
return formatter != null ? formatter : defaultFormatter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copy(Iterable<?> values, String[] formattedValues) {
|
|
||||||
int i = 0;
|
|
||||||
for (Object value : values) {
|
|
||||||
formattedValues[i] = format(value);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BindingResult setValue(Object parsedValue, Object userValue) {
|
|
||||||
try {
|
|
||||||
property.setValue(createEvaluationContext(), parsedValue);
|
|
||||||
return new SuccessResult(property.getExpressionString(), userValue);
|
|
||||||
} catch (EvaluationException e) {
|
|
||||||
return new ExpressionEvaluationErrorResult(property.getExpressionString(), userValue, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private EvaluationContext createEvaluationContext() {
|
|
||||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
|
||||||
context.setRootObject(model);
|
|
||||||
context.addPropertyAccessor(new MapAccessor());
|
|
||||||
context.setTypeConverter(new StandardTypeConverter(typeConverter));
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class getFormattedObjectType(Formatter formatter) {
|
|
||||||
// TODO consider caching this info
|
|
||||||
Class classToIntrospect = formatter.getClass();
|
|
||||||
while (classToIntrospect != null) {
|
|
||||||
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
|
|
||||||
for (Type genericInterface : genericInterfaces) {
|
|
||||||
if (genericInterface instanceof ParameterizedType) {
|
|
||||||
ParameterizedType pInterface = (ParameterizedType) genericInterface;
|
|
||||||
if (Formatter.class.isAssignableFrom((Class) pInterface.getRawType())) {
|
|
||||||
return getParameterClass(pInterface.getActualTypeArguments()[0], formatter.getClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
classToIntrospect = classToIntrospect.getSuperclass();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class getParameterClass(Type parameterType, Class converterClass) {
|
|
||||||
if (parameterType instanceof TypeVariable) {
|
|
||||||
parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass);
|
|
||||||
}
|
|
||||||
if (parameterType instanceof Class) {
|
|
||||||
return (Class) parameterType;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType
|
|
||||||
+ "] on Formatter [" + converterClass.getName() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
static class DefaultFormatter implements Formatter {
|
|
||||||
public String format(Object object, Locale locale) {
|
|
||||||
if (object == null) {
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
return object.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object parse(String formatted, Locale locale) throws ParseException {
|
|
||||||
if (formatted == "") {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NoSuchBindingResult implements BindingResult {
|
|
||||||
|
|
||||||
private String property;
|
|
||||||
|
|
||||||
private Object sourceValue;
|
|
||||||
|
|
||||||
public NoSuchBindingResult(String property, Object sourceValue) {
|
|
||||||
this.property = property;
|
|
||||||
this.sourceValue = sourceValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProperty() {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getSourceValue() {
|
|
||||||
return sourceValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFailure() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Alert getAlert() {
|
|
||||||
return new AbstractAlert() {
|
|
||||||
public String getCode() {
|
|
||||||
return "noSuchBinding";
|
|
||||||
}
|
|
||||||
|
|
||||||
public Severity getSeverity() {
|
|
||||||
return Severity.WARNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
|
||||||
builder.code(getCode());
|
|
||||||
builder.arg("label", new ResolvableArgument(property));
|
|
||||||
builder.arg("value", sourceValue);
|
|
||||||
builder.defaultMessage("Failed to bind to property '" + property
|
|
||||||
+ "'; no binding has been added for the property");
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InvalidFormatResult implements BindingResult {
|
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
||||||
|
|
@ -579,7 +586,7 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
private ParseException cause;
|
private ParseException cause;
|
||||||
|
|
||||||
public InvalidFormatResult(String property, Object sourceValue, ParseException cause) {
|
public InvalidFormat(String property, Object sourceValue, ParseException cause) {
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.sourceValue = sourceValue;
|
this.sourceValue = sourceValue;
|
||||||
this.cause = cause;
|
this.cause = cause;
|
||||||
|
|
@ -621,104 +628,13 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExpressionEvaluationErrorResult implements BindingResult {
|
class Success implements BindingResult {
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
||||||
private Object sourceValue;
|
private Object sourceValue;
|
||||||
|
|
||||||
private EvaluationException cause;
|
public Success(String property, Object sourceValue) {
|
||||||
|
|
||||||
public ExpressionEvaluationErrorResult(String property, Object sourceValue, EvaluationException cause) {
|
|
||||||
this.property = property;
|
|
||||||
this.sourceValue = sourceValue;
|
|
||||||
this.cause = cause;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProperty() {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getSourceValue() {
|
|
||||||
return sourceValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFailure() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Alert getAlert() {
|
|
||||||
return new AbstractAlert() {
|
|
||||||
public String getCode() {
|
|
||||||
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
|
||||||
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
|
||||||
return "conversionFailed";
|
|
||||||
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
|
||||||
// TODO should probably force property exists before even creating binding
|
|
||||||
return "propertyNotFound";
|
|
||||||
} else {
|
|
||||||
return "couldNotSetValue";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Severity getSeverity() {
|
|
||||||
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
|
||||||
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
|
||||||
return Severity.FATAL;
|
|
||||||
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
|
||||||
return Severity.WARNING;
|
|
||||||
} else {
|
|
||||||
return Severity.FATAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
|
||||||
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
|
||||||
AccessException accessException = (AccessException) cause.getCause();
|
|
||||||
if (accessException.getCause() != null) {
|
|
||||||
Throwable cause = accessException.getCause();
|
|
||||||
if (cause instanceof SpelEvaluationException
|
|
||||||
&& ((SpelEvaluationException) cause).getMessageCode() == SpelMessage.TYPE_CONVERSION_ERROR) {
|
|
||||||
ConversionFailedException failure = (ConversionFailedException) cause.getCause();
|
|
||||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
|
||||||
builder.code("conversionFailed");
|
|
||||||
builder.arg("label", new ResolvableArgument(property));
|
|
||||||
builder.arg("value", sourceValue);
|
|
||||||
builder.defaultMessage("Failed to bind to property '" + property + "'; user value "
|
|
||||||
+ StylerUtils.style(sourceValue) + " could not be converted to property type ["
|
|
||||||
+ failure.getTargetType().getName() + "]");
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
|
||||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
|
||||||
builder.code(getCode());
|
|
||||||
builder.arg("label", new ResolvableArgument(property));
|
|
||||||
builder.arg("value", sourceValue);
|
|
||||||
builder.defaultMessage("Failed to bind to property '" + property + "'; no such property exists on model");
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
|
||||||
builder.code("couldNotSetValue");
|
|
||||||
builder.arg("label", new ResolvableArgument(property));
|
|
||||||
builder.arg("value", sourceValue);
|
|
||||||
builder.defaultMessage("Failed to bind to property '" + property + "'; reason = " + cause.getLocalizedMessage());
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SuccessResult implements BindingResult {
|
|
||||||
|
|
||||||
private String property;
|
|
||||||
|
|
||||||
private Object sourceValue;
|
|
||||||
|
|
||||||
public SuccessResult(String property, Object sourceValue) {
|
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.sourceValue = sourceValue;
|
this.sourceValue = sourceValue;
|
||||||
}
|
}
|
||||||
|
|
@ -750,7 +666,8 @@ public class GenericBinder implements Binder {
|
||||||
builder.code("bindSuccess");
|
builder.code("bindSuccess");
|
||||||
builder.arg("label", new ResolvableArgument(property));
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
builder.arg("value", sourceValue);
|
builder.arg("value", sourceValue);
|
||||||
builder.defaultMessage("Successfully bound user value " + StylerUtils.style(sourceValue) + "to property '" + property + "'");
|
builder.defaultMessage("Successfully bound user value " + StylerUtils.style(sourceValue)
|
||||||
|
+ "to property '" + property + "'");
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -758,6 +675,80 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EvaluationError implements BindingResult {
|
||||||
|
|
||||||
|
private String property;
|
||||||
|
|
||||||
|
private Object sourceValue;
|
||||||
|
|
||||||
|
private EvaluationException cause;
|
||||||
|
|
||||||
|
public EvaluationError(String property, Object sourceValue, EvaluationException cause) {
|
||||||
|
this.property = property;
|
||||||
|
this.sourceValue = sourceValue;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getSourceValue() {
|
||||||
|
return sourceValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFailure() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Alert getAlert() {
|
||||||
|
return new AbstractAlert() {
|
||||||
|
public String getCode() {
|
||||||
|
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
||||||
|
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
||||||
|
return "conversionFailed";
|
||||||
|
} else {
|
||||||
|
return "couldNotSetValue";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Severity getSeverity() {
|
||||||
|
return Severity.FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
||||||
|
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
||||||
|
AccessException accessException = (AccessException) cause.getCause();
|
||||||
|
if (accessException.getCause() != null) {
|
||||||
|
Throwable cause = accessException.getCause();
|
||||||
|
if (cause instanceof SpelEvaluationException
|
||||||
|
&& ((SpelEvaluationException) cause).getMessageCode() == SpelMessage.TYPE_CONVERSION_ERROR) {
|
||||||
|
ConversionFailedException failure = (ConversionFailedException) cause.getCause();
|
||||||
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
|
builder.code("conversionFailed");
|
||||||
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.defaultMessage("Failed to bind to property '" + property + "'; user value "
|
||||||
|
+ StylerUtils.style(sourceValue) + " could not be converted to property type ["
|
||||||
|
+ failure.getTargetType().getName() + "]");
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
|
builder.code("couldNotSetValue");
|
||||||
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.defaultMessage("Failed to bind to property '" + property + "'; reason = "
|
||||||
|
+ cause.getLocalizedMessage());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
static abstract class AbstractAlert implements Alert {
|
static abstract class AbstractAlert implements Alert {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getCode() + " - " + getMessage();
|
return getCode() + " - " + getMessage();
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import java.util.Map;
|
||||||
* @see #setDefaultPrefix(String)
|
* @see #setDefaultPrefix(String)
|
||||||
* @see #setPresentPrefix(String)
|
* @see #setPresentPrefix(String)
|
||||||
*/
|
*/
|
||||||
class WebBinder extends GenericBinder {
|
public class WebBinder extends GenericBinder {
|
||||||
|
|
||||||
private String defaultPrefix = "!";
|
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;
|
package org.springframework.ui.lifecycle;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.ui.alert.AlertContext;
|
import org.springframework.ui.alert.AlertContext;
|
||||||
import org.springframework.ui.binding.Binder;
|
import org.springframework.ui.binding.Binder;
|
||||||
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.validation.ValidationFailure;
|
||||||
import org.springframework.ui.validation.Validator;
|
import org.springframework.ui.validation.Validator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,17 +61,28 @@ public final class BindAndValidateLifecycle {
|
||||||
this.validationDecider = validationDecider;
|
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) {
|
public void execute(Map<String, ? extends Object> sourceValues) {
|
||||||
BindingResults bindingResults = binder.bind(sourceValues);
|
BindingResults bindingResults = binder.bind(sourceValues);
|
||||||
if (validator != null && validationDecider.shouldValidateAfter(bindingResults)) {
|
List<ValidationFailure> validationFailures = validate(bindingResults);
|
||||||
// TODO get validation results
|
|
||||||
validator.validate(binder.getModel(), bindingResults.successes().properties());
|
|
||||||
}
|
|
||||||
for (BindingResult result : bindingResults.failures()) {
|
for (BindingResult result : bindingResults.failures()) {
|
||||||
// TODO - you may want to ignore some alerts like propertyNotFound
|
|
||||||
alertContext.add(result.getProperty(), result.getAlert());
|
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;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
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.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.NoSuchBindingException;
|
||||||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||||
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;
|
||||||
|
|
@ -31,6 +32,8 @@ import org.springframework.ui.format.number.CurrencyFormatter;
|
||||||
import org.springframework.ui.format.number.IntegerFormatter;
|
import org.springframework.ui.format.number.IntegerFormatter;
|
||||||
import org.springframework.ui.message.MockMessageSource;
|
import org.springframework.ui.message.MockMessageSource;
|
||||||
|
|
||||||
|
import edu.emory.mathcs.backport.java.util.Arrays;
|
||||||
|
|
||||||
public class GenericBinderTests {
|
public class GenericBinderTests {
|
||||||
|
|
||||||
private TestBean bean;
|
private TestBean bean;
|
||||||
|
|
@ -51,6 +54,10 @@ public class GenericBinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuesWithDefaultTypeConverterConversion() {
|
public void bindSingleValuesWithDefaultTypeConverterConversion() {
|
||||||
|
binder.addBinding("string");
|
||||||
|
binder.addBinding("integer");
|
||||||
|
binder.addBinding("foo");
|
||||||
|
|
||||||
Map<String, String> values = new LinkedHashMap<String, String>();
|
Map<String, String> values = new LinkedHashMap<String, String>();
|
||||||
values.put("string", "test");
|
values.put("string", "test");
|
||||||
values.put("integer", "3");
|
values.put("integer", "3");
|
||||||
|
|
@ -76,7 +83,11 @@ public class GenericBinderTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuesWithDefaultTypeCoversionFailure() {
|
public void bindSingleValuesWithDefaultTypeConversionFailure() {
|
||||||
|
binder.addBinding("string");
|
||||||
|
binder.addBinding("integer");
|
||||||
|
binder.addBinding("foo");
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -90,19 +101,20 @@ public class GenericBinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuePropertyFormatter() throws ParseException {
|
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"));
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValuePropertyFormatterParseException() {
|
public void bindSingleValuePropertyFormatterParseException() {
|
||||||
binder.configureBinding(new BindingConfiguration("date", new DateFormatter()));
|
binder.addBinding("date").formatWith(new DateFormatter());
|
||||||
binder.bind(Collections.singletonMap("date", "bogus"));
|
binder.bind(Collections.singletonMap("date", "bogus"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
|
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
|
||||||
|
binder.addBinding("date");
|
||||||
binder.registerFormatter(Date.class, new DateFormatter());
|
binder.registerFormatter(Date.class, 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());
|
||||||
|
|
@ -110,6 +122,7 @@ public class GenericBinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValueWithFormatterRegisteredByAnnotation() throws ParseException {
|
public void bindSingleValueWithFormatterRegisteredByAnnotation() throws ParseException {
|
||||||
|
binder.addBinding("currency");
|
||||||
GenericFormatterRegistry registry = new GenericFormatterRegistry();
|
GenericFormatterRegistry registry = new GenericFormatterRegistry();
|
||||||
registry.add(CurrencyFormat.class, new CurrencyFormatter());
|
registry.add(CurrencyFormat.class, new CurrencyFormatter());
|
||||||
binder.setFormatterRegistry(registry);
|
binder.setFormatterRegistry(registry);
|
||||||
|
|
@ -118,79 +131,31 @@ public class GenericBinderTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindSingleValueWithnAnnotationFormatterFactoryRegistered() throws ParseException {
|
public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException {
|
||||||
|
binder.addBinding("currency");
|
||||||
binder.registerFormatterFactory(new CurrencyAnnotationFormatterFactory());
|
binder.registerFormatterFactory(new CurrencyAnnotationFormatterFactory());
|
||||||
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
|
@Test(expected = NoSuchBindingException.class)
|
||||||
public void bindSingleValuePropertyNotFound() throws ParseException {
|
public void bindSingleValuePropertyNotFound() throws ParseException {
|
||||||
BindingResults results = binder.bind(Collections.singletonMap("bogus", "2009-06-01"));
|
binder.bind(Collections.singletonMap("bogus", "2009-06-01"));
|
||||||
assertEquals(1, results.size());
|
|
||||||
assertTrue(results.get(0).isFailure());
|
|
||||||
assertEquals("propertyNotFound", results.get(0).getAlert().getCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected=MissingSourceValuesException.class)
|
||||||
public void bindUserValuesCreatedFromUserMap() {
|
public void bindMissingRequiredSourceValue() {
|
||||||
|
binder.addBinding("string");
|
||||||
|
binder.addBinding("integer").required();
|
||||||
Map<String, String> userMap = new LinkedHashMap<String, String>();
|
Map<String, String> userMap = new LinkedHashMap<String, String>();
|
||||||
userMap.put("string", "test");
|
userMap.put("string", "test");
|
||||||
userMap.put("integer", "3");
|
// missing "integer"
|
||||||
BindingResults results = binder.bind(userMap);
|
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingCustomFormatter() {
|
public void getBindingCustomFormatter() {
|
||||||
binder.configureBinding(new BindingConfiguration("currency", new CurrencyFormatter()));
|
binder.addBinding("currency").formatWith(new CurrencyFormatter());
|
||||||
Binding b = binder.getBinding("currency");
|
Binding b = binder.getBinding("currency");
|
||||||
assertFalse(b.isCollection());
|
assertFalse(b.isCollection());
|
||||||
assertEquals("", b.getValue());
|
assertEquals("", b.getValue());
|
||||||
|
|
@ -201,7 +166,7 @@ public class GenericBinderTests {
|
||||||
@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
|
||||||
binder.configureBinding(new BindingConfiguration("integer", new IntegerFormatter()));
|
binder.addBinding("integer").formatWith(new IntegerFormatter());
|
||||||
Binding b = binder.getBinding("integer");
|
Binding b = binder.getBinding("integer");
|
||||||
b.setValue("2,300");
|
b.setValue("2,300");
|
||||||
assertEquals("2,300", b.getValue());
|
assertEquals("2,300", b.getValue());
|
||||||
|
|
@ -210,16 +175,19 @@ public class GenericBinderTests {
|
||||||
@Test
|
@Test
|
||||||
public void invalidFormatBindingResultCustomAlertMessage() {
|
public void invalidFormatBindingResultCustomAlertMessage() {
|
||||||
MockMessageSource messages = new MockMessageSource();
|
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.setMessageSource(messages);
|
||||||
binder.configureBinding(new BindingConfiguration("integer", new IntegerFormatter()));
|
binder.addBinding("integer").formatWith(new IntegerFormatter());
|
||||||
Binding b = binder.getBinding("integer");
|
Binding b = binder.getBinding("integer");
|
||||||
BindingResult result = b.setValue("bogus");
|
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
|
@Test
|
||||||
public void getBindingMultiValued() {
|
public void getBindingMultiValued() {
|
||||||
|
binder.addBinding("foos");
|
||||||
Binding b = binder.getBinding("foos");
|
Binding b = binder.getBinding("foos");
|
||||||
assertTrue(b.isCollection());
|
assertTrue(b.isCollection());
|
||||||
assertEquals(0, b.getCollectionValues().length);
|
assertEquals(0, b.getCollectionValues().length);
|
||||||
|
|
@ -234,8 +202,20 @@ public class GenericBinderTests {
|
||||||
assertEquals("BOOP", values[2]);
|
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
|
@Test
|
||||||
public void getBindingMultiValuedTypeConversionFailure() {
|
public void getBindingMultiValuedTypeConversionFailure() {
|
||||||
|
binder.addBinding("foos");
|
||||||
Binding b = binder.getBinding("foos");
|
Binding b = binder.getBinding("foos");
|
||||||
assertTrue(b.isCollection());
|
assertTrue(b.isCollection());
|
||||||
assertEquals(0, b.getCollectionValues().length);
|
assertEquals(0, b.getCollectionValues().length);
|
||||||
|
|
@ -246,6 +226,11 @@ public class GenericBinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindHandleNullValueInNestedPath() {
|
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>();
|
Map<String, String> values = new LinkedHashMap<String, String>();
|
||||||
|
|
||||||
// EL configured with some options from SpelExpressionParserConfiguration:
|
// EL configured with some options from SpelExpressionParserConfiguration:
|
||||||
|
|
@ -281,7 +266,7 @@ public class GenericBinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void formatPossibleValue() {
|
public void formatPossibleValue() {
|
||||||
binder.configureBinding(new BindingConfiguration("currency", new CurrencyFormatter()));
|
binder.addBinding("currency").formatWith(new CurrencyFormatter());
|
||||||
Binding b = binder.getBinding("currency");
|
Binding b = binder.getBinding("currency");
|
||||||
assertEquals("$5.00", b.format(new BigDecimal("5")));
|
assertEquals("$5.00", b.format(new BigDecimal("5")));
|
||||||
}
|
}
|
||||||
|
|
@ -407,7 +392,8 @@ public class GenericBinderTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CurrencyAnnotationFormatterFactory implements AnnotationFormatterFactory<CurrencyFormat, BigDecimal> {
|
public static class CurrencyAnnotationFormatterFactory implements
|
||||||
|
AnnotationFormatterFactory<CurrencyFormat, BigDecimal> {
|
||||||
public Formatter<BigDecimal> getFormatter(CurrencyFormat annotation) {
|
public Formatter<BigDecimal> getFormatter(CurrencyFormat annotation) {
|
||||||
return new CurrencyFormatter();
|
return new CurrencyFormatter();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,15 @@ public class WebBinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindUserValuesCreatedFromUserMap() throws ParseException {
|
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();
|
GenericFormatterRegistry registry = new GenericFormatterRegistry();
|
||||||
registry.add(CurrencyFormat.class, new CurrencyFormatter());
|
registry.add(CurrencyFormat.class, new CurrencyFormatter());
|
||||||
binder.setFormatterRegistry(registry);
|
binder.setFormatterRegistry(registry);
|
||||||
binder.configureBinding(new BindingConfiguration("date", new DateFormatter()));
|
|
||||||
Map<String, String> userMap = new LinkedHashMap<String, String>();
|
Map<String, String> userMap = new LinkedHashMap<String, String>();
|
||||||
userMap.put("string", "test");
|
userMap.put("string", "test");
|
||||||
userMap.put("_integer", "doesn't matter");
|
userMap.put("_integer", "doesn't matter");
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,8 @@ package org.springframework.ui.lifecycle;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.Alerts;
|
||||||
import org.springframework.ui.alert.Severity;
|
import org.springframework.ui.alert.Severity;
|
||||||
import org.springframework.ui.alert.support.DefaultAlertContext;
|
import org.springframework.ui.alert.support.DefaultAlertContext;
|
||||||
import org.springframework.ui.binding.Binder;
|
|
||||||
import org.springframework.ui.binding.Bound;
|
import org.springframework.ui.binding.Bound;
|
||||||
import org.springframework.ui.binding.Model;
|
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.format.number.CurrencyFormat;
|
||||||
import org.springframework.ui.validation.ValidationResult;
|
import org.springframework.ui.validation.ValidationFailure;
|
||||||
import org.springframework.ui.validation.ValidationResults;
|
|
||||||
import org.springframework.ui.validation.Validator;
|
import org.springframework.ui.validation.Validator;
|
||||||
import org.springframework.ui.validation.constraint.Impact;
|
import org.springframework.ui.validation.constraint.Impact;
|
||||||
import org.springframework.ui.validation.constraint.Message;
|
import org.springframework.ui.validation.constraint.Message;
|
||||||
import org.springframework.ui.validation.constraint.ValidationConstraint;
|
import org.springframework.ui.validation.constraint.ValidationConstraint;
|
||||||
|
|
||||||
|
import edu.emory.mathcs.backport.java.util.Collections;
|
||||||
|
|
||||||
public class BindAndValidateLifecycleTests {
|
public class BindAndValidateLifecycleTests {
|
||||||
|
|
||||||
private BindAndValidateLifecycle lifecycle;
|
private BindAndValidateLifecycle lifecycle;
|
||||||
|
|
@ -40,20 +38,23 @@ public class BindAndValidateLifecycleTests {
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
model = new TestBean();
|
model = new TestBean();
|
||||||
alertContext = new DefaultAlertContext();
|
alertContext = new DefaultAlertContext();
|
||||||
Binder binder = new WebBinderFactory().getBinder(model);
|
WebBinder binder = new WebBinder(model);
|
||||||
Validator<TestBean> validator = new TestBeanValidator();
|
binder.addBinding("string");
|
||||||
|
binder.addBinding("integer");
|
||||||
|
binder.addBinding("foo");
|
||||||
|
Validator validator = new TestBeanValidator();
|
||||||
lifecycle = new BindAndValidateLifecycle(binder, validator, alertContext);
|
lifecycle = new BindAndValidateLifecycle(binder, validator, alertContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TestBeanValidator implements Validator<TestBean> {
|
static class TestBeanValidator implements Validator {
|
||||||
public ValidationResults validate(TestBean model, List<String> properties) {
|
public List<ValidationFailure> validate(Object model, List<String> properties) {
|
||||||
TestValidationResults results = new TestValidationResults();
|
TestBean bean = (TestBean) model;
|
||||||
RequiredConstraint required = new RequiredConstraint();
|
RequiredConstraint required = new RequiredConstraint();
|
||||||
boolean valid = required.validate(model.getString());
|
boolean valid = required.validate(bean);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
|
|
||||||
}
|
}
|
||||||
return results;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,20 +113,8 @@ public class BindAndValidateLifecycleTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TestValidationResults implements ValidationResults {
|
|
||||||
private List<ValidationResult> results = new ArrayList<ValidationResult>();
|
|
||||||
|
|
||||||
public void add(ValidationResult result) {
|
static class TestValidationFailure implements ValidationFailure {
|
||||||
results.add(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<ValidationResult> iterator() {
|
|
||||||
return results.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class TestValidationFailure implements ValidationResult {
|
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
||||||
|
|
@ -143,10 +132,6 @@ public class BindAndValidateLifecycleTests {
|
||||||
return Alerts.error(message);
|
return Alerts.error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFailure() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -292,7 +277,7 @@ public class BindAndValidateLifecycleTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(value="testBean", strictBinding=true)
|
@Model(value="testBean")
|
||||||
public class TestAnnotatedBean {
|
public class TestAnnotatedBean {
|
||||||
|
|
||||||
private String editable;
|
private String editable;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue