diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java index dfc7855bdc..1c9dbcd5c4 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java @@ -25,7 +25,7 @@ public interface Binding { * The formatted value to display in the user interface. */ String getValue(); - + /** * Set the property associated with this binding 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. @@ -51,5 +51,6 @@ public interface Binding { * When a collection binding, the formatted values to display in the user interface. */ String[] getCollectionValues(); - + + } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java index 01de01b864..2991699fa5 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java @@ -55,7 +55,7 @@ import org.springframework.ui.format.Formatter; * Binds user-entered values to properties of a model object. * @author Keith Donald * - * @param The type of model object this binder binds to + * @param The type of model object this binder binds to * @see #add(BindingConfiguration) * @see #bind(Map) */ @@ -214,12 +214,7 @@ public class GenericBinder implements Binder { } public boolean isCollection() { - Class type; - try { - type = getValueType(); - } catch (EvaluationException e) { - throw new IllegalArgumentException("Failed to get property expression value type - this should not happen", e); - } + Class type = getValueType(); TypeDescriptor typeDesc = TypeDescriptor.valueOf(type); return typeDesc.isCollection() || typeDesc.isArray(); } @@ -248,7 +243,19 @@ public class GenericBinder implements Binder { } return formattedValues; } - + + // public impl only + + public Class getValueType() { + 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) { @@ -303,7 +310,7 @@ public class GenericBinder implements Binder { if (formatter != null) { return formatter; } else { - Class type = getValueType(); + Class type = property.getValueType(createEvaluationContext()); Formatter formatter = typeFormatters.get(type); if (formatter != null) { return formatter; @@ -320,9 +327,6 @@ public class GenericBinder implements Binder { } } - private Class getValueType() throws EvaluationException { - return property.getValueType(createEvaluationContext()); - } private Annotation[] getAnnotations() throws EvaluationException { return property.getValueTypeDescriptor(createEvaluationContext()).getAnnotations(); diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/WebBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/WebBinder.java new file mode 100644 index 0000000000..d1a432243e --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/WebBinder.java @@ -0,0 +1,95 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.ui.binding.UserValue; + +/** + * A binder designed for use in HTTP (web) environments. + * Suited for binding user-provided HTTP query parameters to model properties. + * @author Keith Donald + * @param The type of model object this binder binds to + * @see #setFieldDefaultPrefix(String) + * @see #setFieldMarkerPrefix(String) + */ +public class WebBinder extends GenericBinder { + + private String fieldMarkerPrefix = "_"; + + private String fieldDefaultPrefix = "!"; + + /** + * Creates a new web binder for the model object. + * @param model the model object containing properties this binder will bind to + */ + public WebBinder(M model) { + super(model); + } + + /** + * Configure the prefix for determining default field values. + * Default is '!'. + */ + public void setFieldDefaultPrefix(String fieldDefaultPrefix) { + this.fieldDefaultPrefix = fieldDefaultPrefix; + } + + /** + * Configure the prefix for determining empty fields. + * Default is '_'. + */ + public void setFieldMarkerPrefix(String fieldMarkerPrefix) { + this.fieldMarkerPrefix = fieldMarkerPrefix; + } + + @Override + public List createUserValues(Map userMap) { + List values = new ArrayList(); + for (Map.Entry entry : userMap.entrySet()) { + String field = entry.getKey(); + Object value = entry.getValue(); + if (field.startsWith(fieldDefaultPrefix)) { + field = field.substring(fieldDefaultPrefix.length()); + if (!userMap.containsKey(field)) { + values.add(new UserValue(field, value)); + } + } else if (field.startsWith(fieldMarkerPrefix)) { + field = field.substring(fieldMarkerPrefix.length()); + if (!userMap.containsKey(field) && !userMap.containsKey(fieldDefaultPrefix + field)) { + value = getEmptyValue((BindingImpl) getBinding(field)); + values.add(new UserValue(field, value)); + } + } else { + values.add(new UserValue(entry.getKey(), entry.getValue())); + } + } + return values; + } + + protected Object getEmptyValue(BindingImpl binding) { + Class type = binding.getValueType(); + if (boolean.class.equals(type) || Boolean.class.equals(type)) { + return Boolean.FALSE; + } else { + return null; + } + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/WebBinderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/WebBinderTests.java new file mode 100644 index 0000000000..18313747ab --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/WebBinderTests.java @@ -0,0 +1,197 @@ +package org.springframework.ui.binding.support; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.ui.binding.Binder; +import org.springframework.ui.binding.BindingConfiguration; +import org.springframework.ui.binding.BindingResult; +import org.springframework.ui.binding.UserValue; +import org.springframework.ui.format.date.DateFormatter; +import org.springframework.ui.format.number.CurrencyAnnotationFormatterFactory; +import org.springframework.ui.format.number.CurrencyFormat; + +public class WebBinderTests { + + @Before + public void setUp() { + LocaleContextHolder.setLocale(Locale.US); + } + + @After + public void tearDown() { + LocaleContextHolder.setLocale(null); + } + + @Test + public void bindUserValuesCreatedFromUserMap() throws ParseException { + Binder binder = new WebBinder(new TestBean()); + binder.add(new CurrencyAnnotationFormatterFactory()); + binder.add(new BindingConfiguration("date", new DateFormatter())); + Map userMap = new LinkedHashMap(); + userMap.put("string", "test"); + userMap.put("_integer", "doesn't matter"); + userMap.put("_bool", "doesn't matter"); + userMap.put("!date", "2009-06-10"); + userMap.put("!currency", "$5.00"); + userMap.put("_currency", "doesn't matter"); + userMap.put("_addresses", "doesn't matter"); + List values = binder.createUserValues(userMap); + List results = binder.bind(values); + assertEquals(6, results.size()); + assertEquals("test", results.get(0).getUserValue()); + assertEquals(null, results.get(1).getUserValue()); + assertEquals(Boolean.FALSE, results.get(2).getUserValue()); + assertEquals("2009-06-10", results.get(3).getUserValue()); + assertEquals("$5.00", results.get(4).getUserValue()); + assertEquals(null, results.get(5).getUserValue()); + + assertEquals("test", binder.getModel().getString()); + assertEquals(0, binder.getModel().getInteger()); + assertEquals(new DateFormatter().parse("2009-06-10", Locale.US), binder.getModel().getDate()); + assertEquals(false, binder.getModel().isBool()); + assertEquals(new BigDecimal("5.00"), binder.getModel().getCurrency()); + assertEquals(null, binder.getModel().getAddresses()); + } + + public static enum FooEnum { + BAR, BAZ, BOOP; + } + + public static class TestBean { + private String string; + private int integer; + private boolean bool; + private Date date; + private FooEnum foo; + private BigDecimal currency; + private List foos; + private List
addresses; + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } + + public int getInteger() { + return integer; + } + + public void setInteger(int integer) { + this.integer = integer; + } + + public boolean isBool() { + return bool; + } + + public void setBool(boolean bool) { + this.bool = bool; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public FooEnum getFoo() { + return foo; + } + + public void setFoo(FooEnum foo) { + this.foo = foo; + } + + @CurrencyFormat + public BigDecimal getCurrency() { + return currency; + } + + public void setCurrency(BigDecimal currency) { + this.currency = currency; + } + + public List getFoos() { + return foos; + } + + public void setFoos(List foos) { + this.foos = foos; + } + + public List
getAddresses() { + return addresses; + } + + public void setAddresses(List
addresses) { + this.addresses = addresses; + } + + } + + public static class Address { + private String street; + private String city; + private String state; + private String zip; + private String country; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getZip() { + return zip; + } + + public void setZip(String zip) { + this.zip = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + } +}