]> source.dussan.org Git - vaadin-framework.git/commitdiff
Fix bean validation when using sub property bindings (#9248)
authorArtur <artur@vaadin.com>
Mon, 8 May 2017 11:37:21 +0000 (14:37 +0300)
committerHenri Sara <henri.sara@gmail.com>
Mon, 8 May 2017 11:37:21 +0000 (14:37 +0300)
Fixes #9242

server/src/main/java/com/vaadin/data/BeanPropertySet.java
server/src/main/java/com/vaadin/data/BeanValidationBinder.java
server/src/test/java/com/vaadin/data/BeanBinderTest.java

index f78945e9d1bef78fed6548dc5071730e3dce9c5c..0e94b14761243d6d2b338b79acd9f436699ca682 100644 (file)
@@ -21,7 +21,6 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.Arrays;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -201,7 +200,17 @@ public class BeanPropertySet<T> implements PropertySet<T> {
         }
     }
 
-    private static class NestedBeanPropertyDefinition<T, V>
+    /**
+     * Contains properties for a bean type which is nested in another
+     * definition.
+     *
+     * @since
+     * @param <T>
+     *            the bean type
+     * @param <V>
+     *            the value type returned by the getter and set by the setter
+     */
+    public static class NestedBeanPropertyDefinition<T, V>
             extends AbstractBeanPropertyDefinition<T, V> {
 
         private final PropertyDefinition<T, ?> parent;
@@ -250,6 +259,15 @@ public class BeanPropertySet<T> implements PropertySet<T> {
             return new SerializedPropertyDefinition(getPropertySet().beanType,
                     parent.getName() + "." + getName());
         }
+
+        /**
+         * Gets the parent property definition.
+         *
+         * @return the property definition for the parent
+         */
+        public PropertyDefinition<T, ?> getParent() {
+            return parent;
+        }
     }
 
     private static final ConcurrentMap<Class<?>, BeanPropertySet<?>> instances = new ConcurrentHashMap<>();
index c152a67b0cae7dbdf00450f661e155436ebb9232..e125bc98620251d331b120faa30571355b0a374d 100644 (file)
@@ -19,6 +19,7 @@ import javax.validation.metadata.BeanDescriptor;
 import javax.validation.metadata.ConstraintDescriptor;
 import javax.validation.metadata.PropertyDescriptor;
 
+import com.vaadin.data.BeanPropertySet.NestedBeanPropertyDefinition;
 import com.vaadin.data.util.BeanUtil;
 import com.vaadin.data.validator.BeanValidator;
 
@@ -69,7 +70,7 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
      * <p>
      * By default the {@link RequiredFieldConfigurator#DEFAULT} configurator is
      * used.
-     * 
+     *
      * @param configurator
      *            required indicator configurator, may be {@code null}
      */
@@ -80,9 +81,9 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
 
     /**
      * Gets field required indicator configuration logic.
-     * 
+     *
      * @see #setRequiredConfigurator(RequiredFieldConfigurator)
-     * 
+     *
      * @return required indicator configurator, may be {@code null}
      */
     public RequiredFieldConfigurator getRequiredConfigurator() {
@@ -93,7 +94,8 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
     protected BindingBuilder<BEAN, ?> configureBinding(
             BindingBuilder<BEAN, ?> binding,
             PropertyDefinition<BEAN, ?> definition) {
-        BeanValidator validator = new BeanValidator(beanType,
+        Class<?> actualBeanType = findBeanType(beanType, definition);
+        BeanValidator validator = new BeanValidator(actualBeanType,
                 definition.getName());
         if (requiredConfigurator != null) {
             configureRequired(binding, definition, validator);
@@ -101,6 +103,28 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
         return binding.withValidator(validator);
     }
 
+    /**
+     * Finds the bean type containing the property the given definition refers
+     * to.
+     *
+     * @param beanType
+     *            the root beanType
+     * @param definition
+     *            the definition for the property
+     * @return the bean type containing the given property
+     */
+    @SuppressWarnings({ "rawtypes" })
+    private Class<?> findBeanType(Class<BEAN> beanType,
+            PropertyDefinition<BEAN, ?> definition) {
+        if (definition instanceof NestedBeanPropertyDefinition) {
+            return ((NestedBeanPropertyDefinition) definition).getParent()
+                    .getType();
+        } else {
+            // Non nested properties must be defined in the main type
+            return beanType;
+        }
+    }
+
     private void configureRequired(BindingBuilder<BEAN, ?> binding,
             PropertyDefinition<BEAN, ?> definition, BeanValidator validator) {
         assert requiredConfigurator != null;
index d9707c7496da52ace7bc33d2fd102ff008a86d68..f494d71555e2f49056af061988d8da6c6f6e695f 100644 (file)
@@ -17,6 +17,8 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.vaadin.data.BeanBinderTest.RequiredConstraints.SubConstraint;
+import com.vaadin.data.BeanBinderTest.RequiredConstraints.SubSubConstraint;
 import com.vaadin.data.converter.StringToIntegerConverter;
 import com.vaadin.tests.data.bean.BeanToValidate;
 import com.vaadin.ui.CheckBoxGroup;
@@ -33,7 +35,7 @@ public class BeanBinderTest
         private TextField number = new TextField();
     }
 
-    private static class TestBean implements Serializable{
+    private static class TestBean implements Serializable {
         private Set<TestEnum> enums;
         private int number;
 
@@ -54,7 +56,7 @@ public class BeanBinderTest
         }
     }
 
-    public static class RequiredConstraints implements Serializable{
+    public static class RequiredConstraints implements Serializable {
         @NotNull
         @Max(10)
         private String firstname;
@@ -67,7 +69,7 @@ public class BeanBinderTest
         private String lastname;
 
         private SubConstraint subfield;
-        
+
         public String getFirstname() {
             return firstname;
         }
@@ -91,8 +93,7 @@ public class BeanBinderTest
         public void setLastname(String lastname) {
             this.lastname = lastname;
         }
-        
-        
+
         public SubConstraint getSubfield() {
             return subfield;
         }
@@ -101,12 +102,15 @@ public class BeanBinderTest
             this.subfield = subfield;
         }
 
+        public static class SubConstraint implements Serializable {
 
-        public static class SubConstraint implements Serializable{
-            
             @NotNull
+            @NotEmpty
+            @Size(min = 5)
             private String name;
 
+            private SubSubConstraint subsub;
+
             public String getName() {
                 return name;
             }
@@ -114,6 +118,30 @@ public class BeanBinderTest
             public void setName(String name) {
                 this.name = name;
             }
+
+            public SubSubConstraint getSubsub() {
+                return subsub;
+            }
+
+            public void setSubsub(SubSubConstraint subsub) {
+                this.subsub = subsub;
+            }
+
+        }
+
+        public static class SubSubConstraint implements Serializable {
+
+            @Size(min = 10)
+            private String value;
+
+            public String getValue() {
+                return value;
+            }
+
+            public void setValue(String value) {
+                this.value = value;
+            }
+
         }
     }
 
@@ -363,14 +391,14 @@ public class BeanBinderTest
         Assert.assertTrue(field.isRequiredIndicatorVisible());
         testSerialization(binder);
     }
-    
+
     @Test
     public void subfield_name_fieldIsRequired() {
         BeanValidationBinder<RequiredConstraints> binder = new BeanValidationBinder<>(
                 RequiredConstraints.class);
         RequiredConstraints bean = new RequiredConstraints();
         bean.setSubfield(new RequiredConstraints.SubConstraint());
-        
+
         TextField field = new TextField();
         binder.bind(field, "subfield.name");
         binder.setBean(bean);
@@ -379,6 +407,56 @@ public class BeanBinderTest
         testSerialization(binder);
     }
 
+    @Test
+    public void subsubfield_name_fieldIsRequired() {
+        BeanValidationBinder<RequiredConstraints> binder = new BeanValidationBinder<>(
+                RequiredConstraints.class);
+        RequiredConstraints bean = new RequiredConstraints();
+        RequiredConstraints.SubConstraint subfield = new RequiredConstraints.SubConstraint();
+        subfield.setSubsub(new SubSubConstraint());
+        bean.setSubfield(subfield);
+
+        TextField field = new TextField();
+        binder.bind(field, "subfield.subsub.value");
+        binder.setBean(bean);
+
+        Assert.assertTrue(field.isRequiredIndicatorVisible());
+        testSerialization(binder);
+    }
+
+    @Test
+    public void subfield_name_valueCanBeValidated() {
+        BeanValidationBinder<RequiredConstraints> binder = new BeanValidationBinder<>(
+                RequiredConstraints.class);
+        TextField field = new TextField();
+
+        binder.bind(field, "subfield.name");
+        RequiredConstraints bean = new RequiredConstraints();
+        bean.setSubfield(new SubConstraint());
+        binder.setBean(bean);
+        Assert.assertFalse(binder.validate().isOk());
+        field.setValue("overfive");
+        Assert.assertTrue(binder.validate().isOk());
+    }
+
+    @Test
+    public void subSubfield_name_valueCanBeValidated() {
+        BeanValidationBinder<RequiredConstraints> binder = new BeanValidationBinder<>(
+                RequiredConstraints.class);
+        TextField field = new TextField();
+
+        binder.bind(field, "subfield.subsub.value");
+        RequiredConstraints bean = new RequiredConstraints();
+        SubConstraint subfield = new SubConstraint();
+        bean.setSubfield(subfield);
+        subfield.setSubsub(new SubSubConstraint());
+        binder.setBean(bean);
+
+        Assert.assertFalse(binder.validate().isOk());
+        field.setValue("overtencharacters");
+        Assert.assertTrue(binder.validate().isOk());
+    }
+
     private void assertInvalid(HasValue<?> field, String message) {
         BinderValidationStatus<?> status = binder.validate();
         List<BindingValidationStatus<?>> errors = status