diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java
index dc931aba64e..3a4a26ec199 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java
@@ -78,4 +78,18 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor {
*/
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
+ /**
+ * Set if this BeanWrapper should attempt to "auto-grow" a nested path that contains a null value.
+ *
If true, a null path location will be populated with a default object value and traversed
+ * instead of resulting in a {@link NullValueInNestedPathException}. Turning this flag on also
+ * enables auto-growth of collection elements when an index that is out of bounds is accessed.
+ *
Default is false.
+ */
+ void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
+
+ /**
+ * Return whether "auto-growing" of nested paths has been activated.
+ */
+ boolean isAutoGrowNestedPaths();
+
}
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
index 7e9cb2f3310..2be03355dbf 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
@@ -114,7 +114,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
/** The security context used for invoking the property methods */
private AccessControlContext acc;
- private boolean autoGrowNestedPaths;
+ private boolean autoGrowNestedPaths = false;
+
/**
* Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
@@ -252,25 +253,14 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
return (this.rootObject != null ? this.rootObject.getClass() : null);
}
- /**
- * If this BeanWrapper should attempt to "autogrow" a nested path that contains a null value.
- * If true, a null path location will be populated with a default object value and traversed instead of resulting in a {@link NullValueInNestedPathException}.
- * Turning this flag on also enables auto-growth of collection elements when an index that is out of bounds is accessed.
- */
- public boolean getAutoGrowNestedPaths() {
- return this.autoGrowNestedPaths;
- }
-
- /**
- * Sets if this BeanWrapper should attempt to "autogrow" a nested path that contains a null value.
- * If true, a null path location will be populated with a default object value and traversed instead of resulting in a {@link NullValueInNestedPathException}.
- * Turning this flag on also enables auto-growth of collection elements when an index that is out of bounds is accessed.
- * Default is false.
- */
public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
this.autoGrowNestedPaths = autoGrowNestedPaths;
}
+ public boolean isAutoGrowNestedPaths() {
+ return this.autoGrowNestedPaths;
+ }
+
/**
* Set the class to introspect.
* Needs to be called when the target object changes.
@@ -506,9 +496,10 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
String canonicalName = tokens.canonicalName;
Object propertyValue = getPropertyValue(tokens);
if (propertyValue == null) {
- if (autoGrowNestedPaths) {
+ if (this.autoGrowNestedPaths) {
propertyValue = setDefaultValue(tokens);
- } else {
+ }
+ else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
}
}
@@ -561,20 +552,22 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, Array.newInstance(componentType.getComponentType(), 0));
return array;
- } else {
+ }
+ else {
return Array.newInstance(componentType, 0);
}
- } else {
+ }
+ else {
if (Collection.class.isAssignableFrom(type)) {
return CollectionFactory.createCollection(type, 16);
- } else {
+ }
+ else {
return type.newInstance();
}
}
- } catch (InstantiationException e) {
- throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, "Could not instantiate propertyType [" + type.getName() + "] to auto-grow nested property path");
- } catch (IllegalAccessException e) {
- throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, "Could not instantiate propertyType [" + type.getName() + "] to auto-grow nested property path");
+ }
+ catch (Exception ex) {
+ throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex);
}
}
@@ -685,9 +678,10 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
if (tokens.keys != null) {
if (value == null) {
- if (autoGrowNestedPaths) {
+ if (this.autoGrowNestedPaths) {
value = setDefaultValue(tokens.actualName);
- } else {
+ }
+ else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value of property referenced in indexed " +
"property path '" + propertyName + "': returned null");
@@ -775,7 +769,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
}
private Object growArrayIfNecessary(Object array, int index, String name) {
- if (!autoGrowNestedPaths) {
+ if (!this.autoGrowNestedPaths) {
return array;
}
int length = Array.getLength(array);
@@ -794,7 +788,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
}
private void growCollectionIfNecessary(Collection collection, int index, String name, PropertyDescriptor pd, int nestingLevel) {
- if (!autoGrowNestedPaths) {
+ if (!this.autoGrowNestedPaths) {
return;
}
if (index >= collection.size()) {
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
index 85f60173174..055eee2330a 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
@@ -24,6 +24,7 @@ import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
+import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -192,11 +193,7 @@ class TypeConverterDelegate {
// Try to apply some standard type conversion rules if appropriate.
if (convertedValue != null) {
- if (String.class.equals(requiredType) && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
- // We can stringify any primitive value...
- return (T) convertedValue.toString();
- }
- else if (requiredType.isArray()) {
+ if (requiredType.isArray()) {
// Array required -> apply appropriate conversion of elements.
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
@@ -210,6 +207,13 @@ class TypeConverterDelegate {
convertedValue = convertToTypedMap(
(Map) convertedValue, propertyName, requiredType, methodParam);
}
+ if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
+ convertedValue = Array.get(convertedValue, 0);
+ }
+ if (String.class.equals(requiredType) && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
+ // We can stringify any primitive value...
+ return (T) convertedValue.toString();
+ }
else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
if (!requiredType.isInterface() && !requiredType.isEnum()) {
try {
@@ -264,20 +268,19 @@ class TypeConverterDelegate {
private Object attemptToConvertStringToEnum(Class> requiredType, String trimmedValue, Object currentConvertedValue) {
Object convertedValue = currentConvertedValue;
- if(Enum.class.equals(requiredType)) {
+ if (Enum.class.equals(requiredType)) {
// target type is declared as raw enum, treat the trimmed value as .FIELD_NAME
int index = trimmedValue.lastIndexOf(".");
- if(index > - 1) {
+ if (index > - 1) {
String enumType = trimmedValue.substring(0, index);
String fieldName = trimmedValue.substring(index + 1);
-
ClassLoader loader = this.targetObject.getClass().getClassLoader();
-
try {
Class> enumValueType = loader.loadClass(enumType);
Field enumField = enumValueType.getField(fieldName);
convertedValue = enumField.get(null);
- } catch(ClassNotFoundException ex) {
+ }
+ catch (ClassNotFoundException ex) {
if(logger.isTraceEnabled()) {
logger.trace("Enum class [" + enumType + "] cannot be loaded from [" + loader + "]", ex);
}
@@ -289,6 +292,7 @@ class TypeConverterDelegate {
}
}
}
+
if (convertedValue == currentConvertedValue) {
// Try field lookup as fallback: for JDK 1.5 enum or custom enum
// with values defined as static fields. Resulting value still needs
diff --git a/org.springframework.context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java
index e5f2aceadf1..0f77384f4e3 100644
--- a/org.springframework.context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java
+++ b/org.springframework.context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * 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.
@@ -73,6 +73,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
if (this.beanWrapper == null) {
this.beanWrapper = createBeanWrapper();
this.beanWrapper.setExtractOldValueForEditor(true);
+ this.beanWrapper.setAutoGrowNestedPaths(true);
}
return this.beanWrapper;
}
diff --git a/org.springframework.web/src/test/java/org/springframework/beans/TestBean.java b/org.springframework.web/src/test/java/org/springframework/beans/TestBean.java
index 4dd6960743f..282a18352d7 100644
--- a/org.springframework.web/src/test/java/org/springframework/beans/TestBean.java
+++ b/org.springframework.web/src/test/java/org/springframework/beans/TestBean.java
@@ -206,6 +206,14 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
return (spouses != null ? spouses[0] : null);
}
+ public void setConcreteSpouse(TestBean spouse) {
+ this.spouses = new ITestBean[] {spouse};
+ }
+
+ public TestBean getConcreteSpouse() {
+ return (spouses != null ? (TestBean) spouses[0] : null);
+ }
+
public void setSpouse(ITestBean spouse) {
this.spouses = new ITestBean[] {spouse};
}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java b/org.springframework.web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java
new file mode 100644
index 00000000000..9091c9c2f7e
--- /dev/null
+++ b/org.springframework.web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java
@@ -0,0 +1,281 @@
+/*
+ * 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.web.bind.support;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import org.springframework.beans.ITestBean;
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.PropertyValues;
+import org.springframework.beans.TestBean;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.web.bind.ServletRequestParameterPropertyValues;
+import org.springframework.web.context.request.ServletWebRequest;
+
+/**
+ * @author Juergen Hoeller
+ */
+public class WebRequestDataBinderTests {
+
+ @Test
+ public void testBindingWithNestedObjectCreation() throws Exception {
+ TestBean tb = new TestBean();
+
+ WebRequestDataBinder binder = new WebRequestDataBinder(tb, "person");
+ binder.registerCustomEditor(ITestBean.class, new PropertyEditorSupport() {
+ public void setAsText(String text) throws IllegalArgumentException {
+ setValue(new TestBean());
+ }
+ });
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("spouse", "someValue");
+ request.addParameter("spouse.name", "test");
+ binder.bind(new ServletWebRequest(request));
+
+ assertNotNull(tb.getSpouse());
+ assertEquals("test", tb.getSpouse().getName());
+ }
+
+ @Test
+ public void testBindingWithNestedObjectCreationThroughAutoGrow() throws Exception {
+ TestBean tb = new TestBean();
+
+ WebRequestDataBinder binder = new WebRequestDataBinder(tb, "person");
+ binder.setIgnoreUnknownFields(false);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("concreteSpouse.name", "test");
+ binder.bind(new ServletWebRequest(request));
+
+ assertNotNull(tb.getSpouse());
+ assertEquals("test", tb.getSpouse().getName());
+ }
+
+ @Test
+ public void testFieldPrefixCausesFieldReset() throws Exception {
+ TestBean target = new TestBean();
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("_postProcessed", "visible");
+ request.addParameter("postProcessed", "on");
+ binder.bind(new ServletWebRequest(request));
+ assertTrue(target.isPostProcessed());
+
+ request.removeParameter("postProcessed");
+ binder.bind(new ServletWebRequest(request));
+ assertFalse(target.isPostProcessed());
+ }
+
+ @Test
+ public void testFieldPrefixCausesFieldResetWithIgnoreUnknownFields() throws Exception {
+ TestBean target = new TestBean();
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+ binder.setIgnoreUnknownFields(false);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("_postProcessed", "visible");
+ request.addParameter("postProcessed", "on");
+ binder.bind(new ServletWebRequest(request));
+ assertTrue(target.isPostProcessed());
+
+ request.removeParameter("postProcessed");
+ binder.bind(new ServletWebRequest(request));
+ assertFalse(target.isPostProcessed());
+ }
+
+ @Test
+ public void testFieldDefault() throws Exception {
+ TestBean target = new TestBean();
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("!postProcessed", "off");
+ request.addParameter("postProcessed", "on");
+ binder.bind(new ServletWebRequest(request));
+ assertTrue(target.isPostProcessed());
+
+ request.removeParameter("postProcessed");
+ binder.bind(new ServletWebRequest(request));
+ assertFalse(target.isPostProcessed());
+ }
+
+ @Test
+ public void testFieldDefaultPreemptsFieldMarker() throws Exception {
+ TestBean target = new TestBean();
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("!postProcessed", "on");
+ request.addParameter("_postProcessed", "visible");
+ request.addParameter("postProcessed", "on");
+ binder.bind(new ServletWebRequest(request));
+ assertTrue(target.isPostProcessed());
+
+ request.removeParameter("postProcessed");
+ binder.bind(new ServletWebRequest(request));
+ assertTrue(target.isPostProcessed());
+
+ request.removeParameter("!postProcessed");
+ binder.bind(new ServletWebRequest(request));
+ assertFalse(target.isPostProcessed());
+ }
+
+ @Test
+ public void testFieldDefaultNonBoolean() throws Exception {
+ TestBean target = new TestBean();
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("!name", "anonymous");
+ request.addParameter("name", "Scott");
+ binder.bind(new ServletWebRequest(request));
+ assertEquals("Scott", target.getName());
+
+ request.removeParameter("name");
+ binder.bind(new ServletWebRequest(request));
+ assertEquals("anonymous", target.getName());
+ }
+
+ @Test
+ public void testWithCommaSeparatedStringArray() throws Exception {
+ TestBean target = new TestBean();
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("stringArray", "bar");
+ request.addParameter("stringArray", "abc");
+ request.addParameter("stringArray", "123,def");
+ binder.bind(new ServletWebRequest(request));
+ assertEquals("Expected all three items to be bound", 3, target.getStringArray().length);
+
+ request.removeParameter("stringArray");
+ request.addParameter("stringArray", "123,def");
+ binder.bind(new ServletWebRequest(request));
+ assertEquals("Expected only 1 item to be bound", 1, target.getStringArray().length);
+ }
+
+ @Test
+ public void testEnumBinding() {
+ EnumHolder target = new EnumHolder();
+ WebRequestDataBinder binder = new WebRequestDataBinder(target);
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("myEnum", "FOO");
+ binder.bind(new ServletWebRequest(request));
+ assertEquals(MyEnum.FOO, target.getMyEnum());
+ }
+
+ @Test
+ public void testNoPrefix() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("forname", "Tony");
+ request.addParameter("surname", "Blair");
+ request.addParameter("age", "" + 50);
+
+ ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
+ doTestTony(pvs);
+ }
+
+ @Test
+ public void testPrefix() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("test_forname", "Tony");
+ request.addParameter("test_surname", "Blair");
+ request.addParameter("test_age", "" + 50);
+
+ ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
+ assertTrue("Didn't fidn normal when given prefix", !pvs.contains("forname"));
+ assertTrue("Did treat prefix as normal when not given prefix", pvs.contains("test_forname"));
+
+ pvs = new ServletRequestParameterPropertyValues(request, "test");
+ doTestTony(pvs);
+ }
+
+ @Test
+ public void testNoParameters() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
+ assertTrue("Found no parameters", pvs.getPropertyValues().length == 0);
+ }
+
+ @Test
+ public void testMultipleValuesForParameter() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ String[] original = new String[] {"Tony", "Rod"};
+ request.addParameter("forname", original);
+
+ ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
+ assertTrue("Found 1 parameter", pvs.getPropertyValues().length == 1);
+ assertTrue("Found array value", pvs.getPropertyValue("forname").getValue() instanceof String[]);
+ String[] values = (String[]) pvs.getPropertyValue("forname").getValue();
+ assertEquals("Correct values", Arrays.asList(values), Arrays.asList(original));
+ }
+
+ /**
+ * Must contain: forname=Tony surname=Blair age=50
+ */
+ protected void doTestTony(PropertyValues pvs) throws Exception {
+ assertTrue("Contains 3", pvs.getPropertyValues().length == 3);
+ assertTrue("Contains forname", pvs.contains("forname"));
+ assertTrue("Contains surname", pvs.contains("surname"));
+ assertTrue("Contains age", pvs.contains("age"));
+ assertTrue("Doesn't contain tory", !pvs.contains("tory"));
+
+ PropertyValue[] pvArray = pvs.getPropertyValues();
+ Map m = new HashMap();
+ m.put("forname", "Tony");
+ m.put("surname", "Blair");
+ m.put("age", "50");
+ for (PropertyValue pv : pvArray) {
+ Object val = m.get(pv.getName());
+ assertTrue("Can't have unexpected value", val != null);
+ assertTrue("Val i string", val instanceof String);
+ assertTrue("val matches expected", val.equals(pv.getValue()));
+ m.remove(pv.getName());
+ }
+ assertTrue("Map size is 0", m.size() == 0);
+ }
+
+
+ public static class EnumHolder {
+
+ private MyEnum myEnum;
+
+ public MyEnum getMyEnum() {
+ return myEnum;
+ }
+
+ public void setMyEnum(MyEnum myEnum) {
+ this.myEnum = myEnum;
+ }
+ }
+
+
+ public enum MyEnum {
+
+ FOO, BAR
+ }
+
+}