summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorMatti Hosio <mhosio@vaadin.com>2013-07-15 15:56:07 +0300
committerVaadin Code Review <review@vaadin.com>2013-08-09 08:13:00 +0000
commit9f126db566b14afaa8e025056539a38a5d7b9904 (patch)
treeb25d791c4c8a8ddacaf0d85d12cb796ceeb4a4a6 /server
parent437b6de4eea416317bd79399a5dde9870b306975 (diff)
downloadvaadin-framework-9f126db566b14afaa8e025056539a38a5d7b9904.tar.gz
vaadin-framework-9f126db566b14afaa8e025056539a38a5d7b9904.zip
Support for null intermediate beans in NestedMethodProperty (#11435)
Allows intermediate beans to return null in the NestedMethodProperty. The feature is not enabled by default and thus should be fully backwards compatible. Change-Id: I438d0f787c5c76f61ab234f3c92dd927a8354a37
Diffstat (limited to 'server')
-rw-r--r--server/src/com/vaadin/data/util/AbstractBeanContainer.java57
-rw-r--r--server/src/com/vaadin/data/util/BeanItem.java21
-rw-r--r--server/src/com/vaadin/data/util/NestedMethodProperty.java59
-rw-r--r--server/src/com/vaadin/data/util/NestedPropertyDescriptor.java24
-rw-r--r--server/tests/src/com/vaadin/data/util/BeanContainerTest.java24
-rw-r--r--server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java23
-rw-r--r--server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java31
-rw-r--r--server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java16
8 files changed, 250 insertions, 5 deletions
diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
index 35403d6419..cd5c0c809d 100644
--- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
@@ -845,8 +845,32 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
* @return true if the property was added
*/
public boolean addNestedContainerProperty(String propertyId) {
+ return addNestedContainerProperty(propertyId, false);
+ }
+
+ /**
+ * Adds a nested container property for the container, e.g.
+ * "manager.address.street".
+ *
+ * All intermediate getters must exist and must return non-null values when
+ * the property value is accessed or the <code>nullBeansAllowed</code> must
+ * be set to true. If the <code>nullBeansAllowed</code> flag is set to true,
+ * calling getValue of the added property will return null if the property
+ * or any of its intermediate getters returns null. If set to false, null
+ * values returned by intermediate getters will cause NullPointerException.
+ * The default value is false to ensure backwards compatibility.
+ *
+ * @see NestedMethodProperty
+ *
+ * @param propertyId
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @return true if the property was added
+ */
+ public boolean addNestedContainerProperty(String propertyId,
+ boolean nullBeansAllowed) {
return addContainerProperty(propertyId, new NestedPropertyDescriptor(
- propertyId, type));
+ propertyId, type, nullBeansAllowed));
}
/**
@@ -864,13 +888,42 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
*/
@SuppressWarnings("unchecked")
public void addNestedContainerBean(String propertyId) {
+ addNestedContainerBean(propertyId, false);
+ }
+
+ /**
+ * Adds a nested container properties for all sub-properties of a named
+ * property to the container. The named property itself is removed from the
+ * model as its subproperties are added.
+ *
+ * Unless
+ * <code>nullBeansAllowed<code> is set to true, all intermediate getters must
+ * exist and must return non-null values when the property values are
+ * accessed. If the <code>nullBeansAllowed</code> flag is set to true,
+ * calling getValue of the added subproperties will return null if the
+ * property or any of their intermediate getters returns null. If set to
+ * false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @see NestedMethodProperty
+ * @see #addNestedContainerProperty(String)
+ *
+ * @param propertyId
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ @SuppressWarnings("unchecked")
+ public void addNestedContainerBean(String propertyId,
+ boolean nullBeansAllowed) {
Class<?> propertyType = getType(propertyId);
LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem
.getPropertyDescriptors((Class<Object>) propertyType);
for (String subPropertyId : pds.keySet()) {
String qualifiedPropertyId = propertyId + "." + subPropertyId;
NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>(
- qualifiedPropertyId, (Class<BEANTYPE>) type);
+ qualifiedPropertyId, (Class<BEANTYPE>) type,
+ nullBeansAllowed);
model.put(qualifiedPropertyId, pd);
model.remove(propertyId);
for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
diff --git a/server/src/com/vaadin/data/util/BeanItem.java b/server/src/com/vaadin/data/util/BeanItem.java
index fc51be8f36..4834fe4f89 100644
--- a/server/src/com/vaadin/data/util/BeanItem.java
+++ b/server/src/com/vaadin/data/util/BeanItem.java
@@ -268,6 +268,27 @@ public class BeanItem<BT> extends PropertysetItem {
}
/**
+ * Adds a nested property to the item. If the <code>nullBeansAllowed</code>
+ * flag is set to true, calling getValue of the added property will return
+ * null if the property or any of its intermediate getters returns null. If
+ * set to false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @param nestedPropertyId
+ * property id to add. This property must not exist in the item
+ * already and must of of form "field1.field2" where field2 is a
+ * field in the object referenced to by field1
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ public void addNestedProperty(String nestedPropertyId,
+ boolean nullBeansAllowed) {
+ addItemProperty(nestedPropertyId, new NestedMethodProperty<Object>(
+ getBean(), nestedPropertyId, nullBeansAllowed));
+ }
+
+ /**
* Gets the underlying JavaBean object.
*
* @return the bean object.
diff --git a/server/src/com/vaadin/data/util/NestedMethodProperty.java b/server/src/com/vaadin/data/util/NestedMethodProperty.java
index b62ecfbfc3..7a3963c17e 100644
--- a/server/src/com/vaadin/data/util/NestedMethodProperty.java
+++ b/server/src/com/vaadin/data/util/NestedMethodProperty.java
@@ -32,7 +32,7 @@ import com.vaadin.data.util.MethodProperty.MethodException;
* can contain multiple levels of nesting.
*
* When accessing the property value, all intermediate getters must return
- * non-null values.
+ * non-null values or the <code>nullBeansAllowed</code> must be set to true.
*
* @see MethodProperty
*
@@ -55,6 +55,15 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
*/
private Object instance;
+ /**
+ * a boolean flag indicating whether intermediate getters may return null
+ * values. If the flag is set to true, calling getValue will return null if
+ * the property or any of its intermediate getters returns null. If set to
+ * false, intermediate getters returning null value will throw Exception.
+ * The default value is false to ensure backwards compatibility.
+ */
+ private boolean nullBeansAllowed = false;
+
private Class<? extends T> type;
/* Special serialization to handle method references */
@@ -85,7 +94,33 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
* if the property name is invalid
*/
public NestedMethodProperty(Object instance, String propertyName) {
+ this(instance, propertyName, false);
+ }
+
+ /**
+ * Constructs a nested method property for a given object instance. The
+ * property name is a dot separated string pointing to a nested property,
+ * e.g. "manager.address.street". The <code>nullBeansAllowed</code> controls
+ * the behavior in cases where the intermediate getters may return null
+ * values. If the flag is set to true, calling getValue will return null if
+ * the property or any of its intermediate getters returns null. If set to
+ * false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @param instance
+ * top-level bean to which the property applies
+ * @param propertyName
+ * dot separated nested property name
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedMethodProperty(Object instance, String propertyName,
+ boolean nullBeansAllowed) {
this.instance = instance;
+ this.nullBeansAllowed = nullBeansAllowed;
initialize(instance.getClass(), propertyName);
}
@@ -104,6 +139,25 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
}
/**
+ * For internal use to deduce property type etc. without a bean instance.
+ * Calling {@link #setValue(Object)} or {@link #getValue()} on properties
+ * constructed this way is not supported.
+ *
+ * @param instanceClass
+ * class of the top-level bean
+ * @param propertyName
+ * dot separated nested property name
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ NestedMethodProperty(Class<?> instanceClass, String propertyName,
+ boolean nullBeansAllowed) {
+ instance = null;
+ this.nullBeansAllowed = nullBeansAllowed;
+ initialize(instanceClass, propertyName);
+ }
+
+ /**
* Initializes most of the internal fields based on the top-level bean
* instance and property name (dot-separated string).
*
@@ -199,6 +253,9 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
Object object = instance;
for (Method m : getMethods) {
object = m.invoke(object);
+ if (object == null && nullBeansAllowed) {
+ return null;
+ }
}
return (T) object;
} catch (final Throwable e) {
diff --git a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
index b2055fe776..67eb30fae5 100644
--- a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
+++ b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
@@ -34,6 +34,7 @@ public class NestedPropertyDescriptor<BT> implements
private final String name;
private final Class<?> propertyType;
+ private final boolean nullBeansAllowed;
/**
* Creates a property descriptor that can create MethodProperty instances to
@@ -48,10 +49,29 @@ public class NestedPropertyDescriptor<BT> implements
*/
public NestedPropertyDescriptor(String name, Class<BT> beanType)
throws IllegalArgumentException {
+ this(name, beanType, false);
+ }
+
+ /**
+ * Creates a property descriptor that can create MethodProperty instances to
+ * access the underlying bean property.
+ *
+ * @param name
+ * of the property in a dotted path format, e.g. "address.street"
+ * @param beanType
+ * type (class) of the top-level bean
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedPropertyDescriptor(String name, Class<BT> beanType,
+ boolean nullBeansAllowed) throws IllegalArgumentException {
this.name = name;
NestedMethodProperty<?> property = new NestedMethodProperty<Object>(
- beanType, name);
+ beanType, name, nullBeansAllowed);
this.propertyType = property.getType();
+ this.nullBeansAllowed = nullBeansAllowed;
}
@Override
@@ -66,7 +86,7 @@ public class NestedPropertyDescriptor<BT> implements
@Override
public Property<?> createProperty(BT bean) {
- return new NestedMethodProperty<Object>(bean, name);
+ return new NestedMethodProperty<Object>(bean, name, nullBeansAllowed);
}
}
diff --git a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
index 9037e303a8..2dcbb4aed8 100644
--- a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
+++ b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
@@ -457,4 +457,28 @@ public class BeanContainerTest extends AbstractBeanContainerTest {
.getValue());
}
+ public void testNestedContainerPropertyWithNullBean() {
+ BeanContainer<String, NestedMethodPropertyTest.Person> container = new BeanContainer<String, NestedMethodPropertyTest.Person>(
+ NestedMethodPropertyTest.Person.class);
+ container.setBeanIdProperty("name");
+
+ container.addBean(new NestedMethodPropertyTest.Person("John", null));
+ assertTrue(container
+ .addNestedContainerProperty("address.postalCodeObject"));
+ assertTrue(container.addNestedContainerProperty("address.street", true));
+ // the nested properties added with allowNullBean setting should return
+ // null
+ assertNull(container.getContainerProperty("John", "address.street")
+ .getValue());
+ // nested properties added without allowNullBean setting should throw
+ // exception
+ try {
+ container.getContainerProperty("John", "address.postalCodeObject")
+ .getValue();
+ fail();
+ } catch (Exception e) {
+ // should throw exception
+ }
+ }
+
}
diff --git a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java
index 6b88eb336d..3a2cb268b9 100644
--- a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java
+++ b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java
@@ -714,4 +714,27 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest {
.getValue());
}
+ public void testNestedContainerPropertyWithNullBean() {
+ BeanItemContainer<NestedMethodPropertyTest.Person> container = new BeanItemContainer<NestedMethodPropertyTest.Person>(
+ NestedMethodPropertyTest.Person.class);
+ NestedMethodPropertyTest.Person john = new NestedMethodPropertyTest.Person(
+ "John", null);
+ assertNotNull(container.addBean(john));
+ assertTrue(container
+ .addNestedContainerProperty("address.postalCodeObject"));
+ assertTrue(container.addNestedContainerProperty("address.street", true));
+ // the nested properties added with allowNullBean setting should return
+ // null
+ assertNull(container.getContainerProperty(john, "address.street")
+ .getValue());
+ // nested properties added without allowNullBean setting should throw
+ // exception
+ try {
+ container.getContainerProperty(john, "address.postalCodeObject")
+ .getValue();
+ fail();
+ } catch (Exception e) {
+ // should throw exception
+ }
+ }
}
diff --git a/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java
index 640ede8743..d517322010 100644
--- a/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java
+++ b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java
@@ -273,6 +273,23 @@ public class NestedMethodPropertyTest extends TestCase {
Assert.assertEquals("Joonas", managerNameProperty.getValue());
}
+ public void testNullNestedPropertyWithAllowNullBeans() {
+ NestedMethodProperty<String> managerNameProperty = new NestedMethodProperty<String>(
+ vaadin, "manager.name", true);
+ NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
+ vaadin, "manager.address.street", true);
+
+ joonas.setAddress(null);
+ // should return null
+ Assert.assertNull(streetProperty.getValue());
+
+ vaadin.setManager(null);
+ Assert.assertNull(managerNameProperty.getValue());
+ vaadin.setManager(joonas);
+ Assert.assertEquals("Joonas", managerNameProperty.getValue());
+ Assert.assertNull(streetProperty.getValue());
+ }
+
public void testMultiLevelNestedPropertySetValue() {
NestedMethodProperty<String> managerNameProperty = new NestedMethodProperty<String>(
vaadin, "manager.name");
@@ -314,6 +331,20 @@ public class NestedMethodPropertyTest extends TestCase {
Assert.assertEquals("Ruukinkatu 2-4", property2.getValue());
}
+ public void testSerializationWithNullBeansAllowed() throws IOException,
+ ClassNotFoundException {
+ vaadin.setManager(null);
+ NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
+ vaadin, "manager.address.street", true);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ObjectOutputStream(baos).writeObject(streetProperty);
+ @SuppressWarnings("unchecked")
+ NestedMethodProperty<String> property2 = (NestedMethodProperty<String>) new ObjectInputStream(
+ new ByteArrayInputStream(baos.toByteArray())).readObject();
+
+ Assert.assertNull(property2.getValue());
+ }
+
public void testIsReadOnly() {
NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
vaadin, "manager.address.street");
diff --git a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java
index 14e70d76d4..0ae76430f6 100644
--- a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java
+++ b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java
@@ -52,4 +52,20 @@ public class PropertyDescriptorTest extends TestCase {
Property<?> property = pd2.createProperty(new Person("John", null));
Assert.assertEquals("John", property.getValue());
}
+
+ public void testNestedPropertyDescriptorWithNullBeansAllowedSerialization()
+ throws Exception {
+ NestedPropertyDescriptor<Person> pd = new NestedPropertyDescriptor<Person>(
+ "address.street", Person.class, true);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ObjectOutputStream(baos).writeObject(pd);
+ @SuppressWarnings("unchecked")
+ VaadinPropertyDescriptor<Person> pd2 = (VaadinPropertyDescriptor<Person>) new ObjectInputStream(
+ new ByteArrayInputStream(baos.toByteArray())).readObject();
+
+ Property<?> property = pd2.createProperty(new Person("John", null));
+ Assert.assertNull(property.getValue());
+ }
+
}