@Model and @Bound annotations for configuring Binder instance from annotation model beans

This commit is contained in:
Keith Donald
2009-06-23 17:53:16 +00:00
parent 13c3c577eb
commit f1b936515f
7 changed files with 190 additions and 16 deletions

View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -20,6 +20,6 @@ public @interface Model {
* Configures strict model binding.
* @see Binder#setStrict(boolean)
*/
boolean strict() default false;
boolean strictBinding() default false;
}

View File

@@ -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());
results.add(binding.setValue(value.getValue()));
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();
}
}
}

View File

@@ -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,13 +51,12 @@ 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;
}
public void setFormatterRegistry(FormatterRegistry 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
}
}
}