@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 {
|
||||
|
||||
@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) {
|
||||
return new ArrayList<Alert>();
|
||||
}
|
||||
|
|
@ -43,11 +43,11 @@ public class DefaultAlertContext implements AlertContext {
|
|||
// implementing AlertContext
|
||||
|
||||
public Map<String, List<Alert>> getAlerts() {
|
||||
return Collections.unmodifiableMap(alertsByElement);
|
||||
return Collections.unmodifiableMap(alerts);
|
||||
}
|
||||
|
||||
public List<Alert> getAlerts(String element) {
|
||||
List<Alert> messages = alertsByElement.get(element);
|
||||
List<Alert> messages = alerts.get(element);
|
||||
if (messages.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
|
@ -55,12 +55,12 @@ public class DefaultAlertContext implements AlertContext {
|
|||
}
|
||||
|
||||
public void add(Alert alert) {
|
||||
List<Alert> alerts = alertsByElement.get(alert.getElement());
|
||||
List<Alert> alerts = this.alerts.get(alert.getElement());
|
||||
alerts.add(alert);
|
||||
}
|
||||
|
||||
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.Target;
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Bound {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ public @interface Model {
|
|||
* Configures strict model binding.
|
||||
* @see Binder#setStrict(boolean)
|
||||
*/
|
||||
boolean strict() default false;
|
||||
boolean strictBinding() default false;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ public class GenericBinder implements Binder {
|
|||
return model;
|
||||
}
|
||||
|
||||
public boolean isStrict() {
|
||||
return strict;
|
||||
}
|
||||
|
||||
public void setStrict(boolean strict) {
|
||||
this.strict = strict;
|
||||
}
|
||||
|
|
@ -148,7 +152,11 @@ public class GenericBinder implements Binder {
|
|||
ArrayListBindingResults results = new ArrayListBindingResults(values.size());
|
||||
for (UserValue value : values) {
|
||||
BindingImpl binding = (BindingImpl) getBinding(value.getProperty());
|
||||
if (binding != null) {
|
||||
results.add(binding.setValue(value.getValue()));
|
||||
} else {
|
||||
results.add(new NoSuchBindingResult(value));
|
||||
}
|
||||
}
|
||||
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 {
|
||||
|
||||
private String property;
|
||||
|
|
@ -473,7 +522,7 @@ public class GenericBinder implements Binder {
|
|||
}
|
||||
|
||||
public Alert getAlert() {
|
||||
return new Alert() {
|
||||
return new AbstractAlert() {
|
||||
public String getElement() {
|
||||
// TODO append model first? e.g. model.property
|
||||
return getProperty();
|
||||
|
|
@ -523,7 +572,7 @@ public class GenericBinder implements Binder {
|
|||
}
|
||||
|
||||
public Alert getAlert() {
|
||||
return new Alert() {
|
||||
return new AbstractAlert() {
|
||||
public String getElement() {
|
||||
// TODO append model first? e.g. model.property
|
||||
return getProperty();
|
||||
|
|
@ -611,7 +660,7 @@ public class GenericBinder implements Binder {
|
|||
}
|
||||
|
||||
public Alert getAlert() {
|
||||
return new Alert() {
|
||||
return new AbstractAlert() {
|
||||
public String getElement() {
|
||||
// TODO append model first? e.g. model.property
|
||||
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;
|
||||
|
||||
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 org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.ui.alert.AlertContext;
|
||||
import org.springframework.ui.binding.BindingConfiguration;
|
||||
import org.springframework.ui.binding.BindingResult;
|
||||
import org.springframework.ui.binding.BindingResults;
|
||||
import org.springframework.ui.binding.Bound;
|
||||
import org.springframework.ui.binding.FormatterRegistry;
|
||||
import org.springframework.ui.binding.Model;
|
||||
import org.springframework.ui.binding.UserValues;
|
||||
import org.springframework.ui.binding.support.WebBinder;
|
||||
import org.springframework.ui.validation.Validator;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implementation of the bind and validate lifecycle for web (HTTP) environments.
|
||||
|
|
@ -41,10 +51,9 @@ public class WebBindAndValidateLifecycle {
|
|||
private Validator validator;
|
||||
|
||||
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);
|
||||
// TODO this doesn't belong in here
|
||||
configure(binder, model);
|
||||
this.alertContext = alertContext;
|
||||
}
|
||||
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
@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
|
||||
public void getBindingCustomFormatter() {
|
||||
binder.add(new BindingConfiguration("currency", new CurrencyFormatter()));
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
import org.springframework.ui.alert.Severity;
|
||||
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.format.number.CurrencyFormat;
|
||||
import org.springframework.ui.format.number.IntegerFormatter;
|
||||
|
||||
public class WebBindAndLifecycleTests {
|
||||
public class WebBindAndValidateLifecycleTests {
|
||||
|
||||
private WebBindAndValidateLifecycle lifecycle;
|
||||
|
||||
|
|
@ -68,6 +70,37 @@ public class WebBindAndLifecycleTests {
|
|||
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 {
|
||||
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