@Model and @Bound annotations for configuring Binder instance from annotation model beans
This commit is contained in:
parent
13c3c577eb
commit
f1b936515f
|
|
@ -34,7 +34,7 @@ import org.springframework.util.CachingMapDecorator;
|
||||||
public class DefaultAlertContext implements AlertContext {
|
public class DefaultAlertContext implements AlertContext {
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private Map<String, List<Alert>> alertsByElement = new CachingMapDecorator<String, List<Alert>>(new LinkedHashMap<String, List<Alert>>()) {
|
private Map<String, List<Alert>> alerts = new CachingMapDecorator<String, List<Alert>>(new LinkedHashMap<String, List<Alert>>()) {
|
||||||
protected List<Alert> create(String element) {
|
protected List<Alert> create(String element) {
|
||||||
return new ArrayList<Alert>();
|
return new ArrayList<Alert>();
|
||||||
}
|
}
|
||||||
|
|
@ -43,11 +43,11 @@ public class DefaultAlertContext implements AlertContext {
|
||||||
// implementing AlertContext
|
// implementing AlertContext
|
||||||
|
|
||||||
public Map<String, List<Alert>> getAlerts() {
|
public Map<String, List<Alert>> getAlerts() {
|
||||||
return Collections.unmodifiableMap(alertsByElement);
|
return Collections.unmodifiableMap(alerts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Alert> getAlerts(String element) {
|
public List<Alert> getAlerts(String element) {
|
||||||
List<Alert> messages = alertsByElement.get(element);
|
List<Alert> messages = alerts.get(element);
|
||||||
if (messages.isEmpty()) {
|
if (messages.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
@ -55,12 +55,12 @@ public class DefaultAlertContext implements AlertContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(Alert alert) {
|
public void add(Alert alert) {
|
||||||
List<Alert> alerts = alertsByElement.get(alert.getElement());
|
List<Alert> alerts = this.alerts.get(alert.getElement());
|
||||||
alerts.add(alert);
|
alerts.add(alert);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringCreator(this).append("alertsByElement", alertsByElement).toString();
|
return new ToStringCreator(this).append("alerts", alerts).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Target({ElementType.METHOD})
|
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
public @interface Bound {
|
public @interface Bound {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,6 @@ public @interface Model {
|
||||||
* Configures strict model binding.
|
* Configures strict model binding.
|
||||||
* @see Binder#setStrict(boolean)
|
* @see Binder#setStrict(boolean)
|
||||||
*/
|
*/
|
||||||
boolean strict() default false;
|
boolean strictBinding() default false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,10 @@ public class GenericBinder implements Binder {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isStrict() {
|
||||||
|
return strict;
|
||||||
|
}
|
||||||
|
|
||||||
public void setStrict(boolean strict) {
|
public void setStrict(boolean strict) {
|
||||||
this.strict = strict;
|
this.strict = strict;
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +152,11 @@ public class GenericBinder implements Binder {
|
||||||
ArrayListBindingResults results = new ArrayListBindingResults(values.size());
|
ArrayListBindingResults results = new ArrayListBindingResults(values.size());
|
||||||
for (UserValue value : values) {
|
for (UserValue value : values) {
|
||||||
BindingImpl binding = (BindingImpl) getBinding(value.getProperty());
|
BindingImpl binding = (BindingImpl) getBinding(value.getProperty());
|
||||||
results.add(binding.setValue(value.getValue()));
|
if (binding != null) {
|
||||||
|
results.add(binding.setValue(value.getValue()));
|
||||||
|
} else {
|
||||||
|
results.add(new NoSuchBindingResult(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
@ -449,6 +457,47 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class NoSuchBindingResult implements BindingResult {
|
||||||
|
private UserValue userValue;
|
||||||
|
|
||||||
|
public NoSuchBindingResult(UserValue userValue) {
|
||||||
|
this.userValue = userValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty() {
|
||||||
|
return userValue.getProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getUserValue() {
|
||||||
|
return userValue.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFailure() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Alert getAlert() {
|
||||||
|
return new AbstractAlert() {
|
||||||
|
public String getElement() {
|
||||||
|
// TODO append model first? e.g. model.property
|
||||||
|
return getProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return "noSuchBinding";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Severity getSeverity() {
|
||||||
|
return Severity.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return "Failed to bind to property '" + userValue.getProperty() + "'; no binding has been added for the property";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class InvalidFormatResult implements BindingResult {
|
static class InvalidFormatResult implements BindingResult {
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
@ -473,7 +522,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Alert getAlert() {
|
public Alert getAlert() {
|
||||||
return new Alert() {
|
return new AbstractAlert() {
|
||||||
public String getElement() {
|
public String getElement() {
|
||||||
// TODO append model first? e.g. model.property
|
// TODO append model first? e.g. model.property
|
||||||
return getProperty();
|
return getProperty();
|
||||||
|
|
@ -523,7 +572,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Alert getAlert() {
|
public Alert getAlert() {
|
||||||
return new Alert() {
|
return new AbstractAlert() {
|
||||||
public String getElement() {
|
public String getElement() {
|
||||||
// TODO append model first? e.g. model.property
|
// TODO append model first? e.g. model.property
|
||||||
return getProperty();
|
return getProperty();
|
||||||
|
|
@ -611,7 +660,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Alert getAlert() {
|
public Alert getAlert() {
|
||||||
return new Alert() {
|
return new AbstractAlert() {
|
||||||
public String getElement() {
|
public String getElement() {
|
||||||
// TODO append model first? e.g. model.property
|
// TODO append model first? e.g. model.property
|
||||||
return getProperty();
|
return getProperty();
|
||||||
|
|
@ -632,4 +681,10 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static abstract class AbstractAlert implements Alert {
|
||||||
|
public String toString() {
|
||||||
|
return getElement() + ":" + getCode() + " - " + getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,25 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.ui.lifecycle;
|
package org.springframework.ui.lifecycle;
|
||||||
|
|
||||||
|
import java.beans.BeanInfo;
|
||||||
|
import java.beans.IntrospectionException;
|
||||||
|
import java.beans.Introspector;
|
||||||
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.ui.alert.AlertContext;
|
import org.springframework.ui.alert.AlertContext;
|
||||||
|
import org.springframework.ui.binding.BindingConfiguration;
|
||||||
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.Bound;
|
||||||
import org.springframework.ui.binding.FormatterRegistry;
|
import org.springframework.ui.binding.FormatterRegistry;
|
||||||
|
import org.springframework.ui.binding.Model;
|
||||||
import org.springframework.ui.binding.UserValues;
|
import org.springframework.ui.binding.UserValues;
|
||||||
import org.springframework.ui.binding.support.WebBinder;
|
import org.springframework.ui.binding.support.WebBinder;
|
||||||
import org.springframework.ui.validation.Validator;
|
import org.springframework.ui.validation.Validator;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the bind and validate lifecycle for web (HTTP) environments.
|
* Implementation of the bind and validate lifecycle for web (HTTP) environments.
|
||||||
|
|
@ -41,13 +51,12 @@ public class WebBindAndValidateLifecycle {
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
|
||||||
public WebBindAndValidateLifecycle(Object model, AlertContext alertContext) {
|
public WebBindAndValidateLifecycle(Object model, AlertContext alertContext) {
|
||||||
// TODO allow binder to be configured with bindings from @Model metadata
|
|
||||||
// TODO support @Bound property annotation?
|
|
||||||
// TODO support @StrictBinding class-level annotation?
|
|
||||||
this.binder = new WebBinder(model);
|
this.binder = new WebBinder(model);
|
||||||
|
// TODO this doesn't belong in here
|
||||||
|
configure(binder, model);
|
||||||
this.alertContext = alertContext;
|
this.alertContext = alertContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFormatterRegistry(FormatterRegistry registry) {
|
public void setFormatterRegistry(FormatterRegistry registry) {
|
||||||
binder.setFormatterRegistry(registry);
|
binder.setFormatterRegistry(registry);
|
||||||
}
|
}
|
||||||
|
|
@ -76,4 +85,35 @@ public class WebBindAndValidateLifecycle {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
|
||||||
|
private void configure(WebBinder binder, Object model) {
|
||||||
|
Model m = AnnotationUtils.findAnnotation(model.getClass(), Model.class);
|
||||||
|
if (m != null) {
|
||||||
|
if (StringUtils.hasText(m.value())) {
|
||||||
|
// TODO model name setting
|
||||||
|
//binder.setModelName(m.value());
|
||||||
|
}
|
||||||
|
binder.setStrict(m.strictBinding());
|
||||||
|
}
|
||||||
|
if (binder.isStrict()) {
|
||||||
|
BeanInfo beanInfo;
|
||||||
|
try {
|
||||||
|
beanInfo = Introspector.getBeanInfo(model.getClass());
|
||||||
|
} catch (IntrospectionException e) {
|
||||||
|
throw new IllegalStateException("Unable to introspect model " + model, e);
|
||||||
|
}
|
||||||
|
// TODO do we have to still flush introspector cache here?
|
||||||
|
for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
|
||||||
|
Method getter = prop.getReadMethod();
|
||||||
|
Bound b = AnnotationUtils.getAnnotation(getter, Bound.class);
|
||||||
|
if (b != null) {
|
||||||
|
// TODO should we wire formatter here if using a format annotation - an optimization?
|
||||||
|
binder.add(new BindingConfiguration(prop.getName(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO @Bound fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,25 @@ public class GenericBinderTests {
|
||||||
assertFalse(result.isFailure());
|
assertFalse(result.isFailure());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bindStrictNoMappingBindings() {
|
||||||
|
binder.setStrict(true);
|
||||||
|
binder.add(new BindingConfiguration("integer", null));
|
||||||
|
UserValues values = new UserValues();
|
||||||
|
values.add("integer", "3");
|
||||||
|
values.add("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).getUserValue());
|
||||||
|
|
||||||
|
assertEquals("foo", results.get(1).getProperty());
|
||||||
|
assertTrue(results.get(1).isFailure());
|
||||||
|
assertEquals("BAR", results.get(1).getUserValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBindingCustomFormatter() {
|
public void getBindingCustomFormatter() {
|
||||||
binder.add(new BindingConfiguration("currency", new CurrencyFormatter()));
|
binder.add(new BindingConfiguration("currency", new CurrencyFormatter()));
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,13 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
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.Bound;
|
||||||
|
import org.springframework.ui.binding.Model;
|
||||||
import org.springframework.ui.binding.support.GenericFormatterRegistry;
|
import org.springframework.ui.binding.support.GenericFormatterRegistry;
|
||||||
import org.springframework.ui.format.number.CurrencyFormat;
|
import org.springframework.ui.format.number.CurrencyFormat;
|
||||||
import org.springframework.ui.format.number.IntegerFormatter;
|
import org.springframework.ui.format.number.IntegerFormatter;
|
||||||
|
|
||||||
public class WebBindAndLifecycleTests {
|
public class WebBindAndValidateLifecycleTests {
|
||||||
|
|
||||||
private WebBindAndValidateLifecycle lifecycle;
|
private WebBindAndValidateLifecycle lifecycle;
|
||||||
|
|
||||||
|
|
@ -67,6 +69,37 @@ public class WebBindAndLifecycleTests {
|
||||||
assertEquals(Severity.ERROR, alertContext.getAlerts("integer").get(0).getSeverity());
|
assertEquals(Severity.ERROR, alertContext.getAlerts("integer").get(0).getSeverity());
|
||||||
assertEquals("Failed to bind to property 'integer'; the user value 'bogus' has an invalid format and could no be parsed", alertContext.getAlerts("integer").get(0).getMessage());
|
assertEquals("Failed to bind to property 'integer'; the user value 'bogus' has an invalid format and could no be parsed", alertContext.getAlerts("integer").get(0).getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteLifecycleAnnotatedModel() {
|
||||||
|
TestAnnotatedBean model = new TestAnnotatedBean();
|
||||||
|
lifecycle = new WebBindAndValidateLifecycle(model, alertContext);
|
||||||
|
Map<String, Object> userMap = new HashMap<String, Object>();
|
||||||
|
GenericFormatterRegistry registry = new GenericFormatterRegistry();
|
||||||
|
registry.add(new IntegerFormatter(), Integer.class);
|
||||||
|
lifecycle.setFormatterRegistry(registry);
|
||||||
|
userMap.put("editable", "foo");
|
||||||
|
lifecycle.execute(userMap);
|
||||||
|
assertEquals(0, alertContext.getAlerts().size());
|
||||||
|
assertEquals("foo", model.getEditable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteLifecycleAnnotatedModelNonEditableBindingAttempt() {
|
||||||
|
TestAnnotatedBean model = new TestAnnotatedBean();
|
||||||
|
lifecycle = new WebBindAndValidateLifecycle(model, alertContext);
|
||||||
|
Map<String, Object> userMap = new HashMap<String, Object>();
|
||||||
|
GenericFormatterRegistry registry = new GenericFormatterRegistry();
|
||||||
|
registry.add(new IntegerFormatter(), Integer.class);
|
||||||
|
lifecycle.setFormatterRegistry(registry);
|
||||||
|
userMap.put("editable", "foo");
|
||||||
|
userMap.put("nonEditable", "whatev");
|
||||||
|
lifecycle.execute(userMap);
|
||||||
|
assertEquals(1, alertContext.getAlerts().size());
|
||||||
|
assertEquals("foo", model.getEditable());
|
||||||
|
assertEquals(null, model.getNotEditable());
|
||||||
|
assertEquals("noSuchBinding", alertContext.getAlerts("nonEditable").get(0).getCode());
|
||||||
|
}
|
||||||
|
|
||||||
public static enum FooEnum {
|
public static enum FooEnum {
|
||||||
BAR, BAZ, BOOP;
|
BAR, BAZ, BOOP;
|
||||||
|
|
@ -188,4 +221,31 @@ public class WebBindAndLifecycleTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Model(value="testBean", strictBinding=true)
|
||||||
|
public class TestAnnotatedBean {
|
||||||
|
|
||||||
|
private String editable;
|
||||||
|
|
||||||
|
private String notEditable;
|
||||||
|
|
||||||
|
@Bound
|
||||||
|
public String getEditable() {
|
||||||
|
return editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEditable(String editable) {
|
||||||
|
this.editable = editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotEditable() {
|
||||||
|
return notEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotEditable(String notEditable) {
|
||||||
|
this.notEditable = notEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue