You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by ro...@apache.org on 2010/05/05 10:58:59 UTC

svn commit: r941207 - in /incubator/bval/trunk/bval-jsr303/src: main/java/org/apache/bval/jsr303/ main/java/org/apache/bval/jsr303/util/ test/java/org/apache/bval/constraints/ test/java/org/apache/bval/jsr303/

Author: romanstumm
Date: Wed May  5 08:58:58 2010
New Revision: 941207

URL: http://svn.apache.org/viewvc?rev=941207&view=rev
Log:
BVAL-37  Ensure constraint definitions are valid - from Carlos Varla

Added:
    incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/ConstraintDefinitionValidator.java
Modified:
    incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToBuilder.java
    incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/Jsr303MetaBeanFactory.java
    incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/constraints/Password.java
    incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/jsr303/ConstraintDefinitionsTest.java

Modified: incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToBuilder.java
URL: http://svn.apache.org/viewvc/incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToBuilder.java?rev=941207&r1=941206&r2=941207&view=diff
==============================================================================
--- incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToBuilder.java (original)
+++ incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToBuilder.java Wed May  5 08:58:58 2010
@@ -61,14 +61,14 @@ public class AppendValidationToBuilder e
     /**
      * @return The set of groups from the parent constraint.
      */
-    public Set<?> getInheritedGroups() {
+    public Set<Class<?>> getInheritedGroups() {
         return builder.getConstraintValidation().getGroups();
     }
     
     /**
      * @return The set of payloads from the parent constraint.
      */
-    public Set<?> getInheritedPayload() {
+    public Set<Class<? extends Payload>> getInheritedPayload() {
         return builder.getConstraintValidation().getPayload();
     }
     

Modified: incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/Jsr303MetaBeanFactory.java
URL: http://svn.apache.org/viewvc/incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/Jsr303MetaBeanFactory.java?rev=941207&r1=941206&r2=941207&view=diff
==============================================================================
--- incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/Jsr303MetaBeanFactory.java (original)
+++ incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/Jsr303MetaBeanFactory.java Wed May  5 08:58:58 2010
@@ -21,6 +21,7 @@ package org.apache.bval.jsr303;
 
 import org.apache.bval.MetaBeanFactory;
 import org.apache.bval.jsr303.groups.Group;
+import org.apache.bval.jsr303.util.ConstraintDefinitionValidator;
 import org.apache.bval.jsr303.util.SecureActions;
 import org.apache.bval.jsr303.util.TypeUtils;
 import org.apache.bval.jsr303.xml.MetaConstraint;
@@ -304,6 +305,7 @@ public class Jsr303MetaBeanFactory imple
              */
             Constraint vcAnno = annotation.annotationType().getAnnotation(Constraint.class);
             if (vcAnno != null) {
+                ConstraintDefinitionValidator.validateConstraintDefinition(annotation);
                 Class<? extends ConstraintValidator<?, ?>>[] validatorClasses;
                 validatorClasses = findConstraintValidatorClasses(annotation, vcAnno);
                 return applyConstraint(annotation, validatorClasses, prop, owner, access,

Added: incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/ConstraintDefinitionValidator.java
URL: http://svn.apache.org/viewvc/incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/ConstraintDefinitionValidator.java?rev=941207&view=auto
==============================================================================
--- incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/ConstraintDefinitionValidator.java (added)
+++ incubator/bval/trunk/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/ConstraintDefinitionValidator.java Wed May  5 08:58:58 2010
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.bval.jsr303.util;
+
+import javax.validation.Constraint;
+import javax.validation.ConstraintDefinitionException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+/**
+ * Internal validator that ensures the correct definition of constraint
+ * annotations.
+ * 
+ * @author Carlos Vara
+ */
+public class ConstraintDefinitionValidator {
+
+    /**
+     * Ensures that the constraint definition is valid.
+     * 
+     * @param annotation
+     *            An annotation which is annotated with {@link Constraint}.
+     * @throws ConstraintDefinitionException
+     *             In case the constraint is invalid.
+     */
+    public static void validateConstraintDefinition(Annotation annotation) {
+        validGroups(annotation);
+        validPayload(annotation);
+        validMessage(annotation);
+        validAttributes(annotation);
+    }
+    
+    /**
+     * Check that the annotation:
+     * <ul>
+     * <li>Has a groups() method.</li>
+     * <li>Whose default value is an empty Class[] array.</li>
+     * </ul>
+     * 
+     * @param annotation
+     *            The annotation to check.
+     */
+    private static void validGroups(Annotation annotation) {
+        // Ensure that it has a groups() method...
+        Method groupsMethod = SecureActions.getMethod(annotation.annotationType(), "groups");
+        if ( groupsMethod == null ) {
+            throw new ConstraintDefinitionException("Constraint definition " + annotation + " has no groups() method");
+        }
+        
+        // ...whose default value is an empty array
+        Object defaultGroupsValue = groupsMethod.getDefaultValue();
+        if ( defaultGroupsValue instanceof Class<?>[] ) {
+            if ( ((Class[]) defaultGroupsValue).length != 0 ) {
+                throw new ConstraintDefinitionException("Default value for groups() must be an empty array");
+            }
+        }
+        else {
+            throw new ConstraintDefinitionException("Return type for groups() must be of type Class<?>[]");
+        }
+    }
+    
+    /**
+     * Check that the annotation:
+     * <ul>
+     * <li>Has a payload() method.</li>
+     * <li>Whose default value is an empty Class[] array.</li>
+     * </ul>
+     * 
+     * @param annotation
+     *            The annotation to check.
+     */
+    private static void validPayload(Annotation annotation) {
+        // Ensure that it has a payload() method...
+        Method payloadMethod = SecureActions.getMethod(annotation.annotationType(), "payload");
+        if ( payloadMethod == null ) {
+            throw new ConstraintDefinitionException("Constraint definition " + annotation + " has no payload() method");
+        }
+        
+        // ...whose default value is an empty array
+        Object defaultPayloadValue = payloadMethod.getDefaultValue();
+        if ( defaultPayloadValue instanceof Class<?>[] ) {
+            if ( ((Class[]) defaultPayloadValue).length != 0 ) {
+                throw new ConstraintDefinitionException("Default value for payload() must be an empty array");
+            }
+        }
+        else {
+            throw new ConstraintDefinitionException("Return type for payload() must be of type Class<? extends Payload>[]");
+        }
+    }
+    
+    /**
+     * Check that the annotation:
+     * <ul>
+     * <li>Has a message() method.</li>
+     * <li>Whose default value is a {@link String}.</li>
+     * </ul>
+     * 
+     * @param annotation
+     *            The annotation to check.
+     */
+    private static void validMessage(Annotation annotation) {
+        // Ensure that it has a message() method...
+        Method messageMethod = SecureActions.getMethod(annotation.annotationType(), "message");
+        if ( messageMethod == null ) {
+            throw new ConstraintDefinitionException("Constraint definition " + annotation + " has no message() method");
+        }
+        
+        // ...whose default value is a String
+        Object defaultMessageValue = messageMethod.getDefaultValue();
+        if ( !(defaultMessageValue instanceof String) ) {
+            throw new ConstraintDefinitionException("Return type for message() must be of type String");
+        }
+    }
+    
+    /**
+     * Check that the annotation has no methods that start with "valid".
+     * 
+     * @param annotation
+     *            The annotation to check.
+     */
+    private static void validAttributes(Annotation annotation) {
+        Method[] methods = SecureActions.getDeclaredMethods(annotation.annotationType());
+        for ( Method method : methods ) {
+            // Currently case insensitive, the spec is unclear about this
+            if ( method.getName().toLowerCase(Locale.ENGLISH).startsWith("valid") ) {
+                throw new ConstraintDefinitionException("A constraint annotation cannot have methods which start with 'valid'");
+            }
+        }
+    }
+
+}

Modified: incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/constraints/Password.java
URL: http://svn.apache.org/viewvc/incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/constraints/Password.java?rev=941207&r1=941206&r2=941207&view=diff
==============================================================================
--- incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/constraints/Password.java (original)
+++ incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/constraints/Password.java Wed May  5 08:58:58 2010
@@ -22,10 +22,8 @@ import javax.validation.Constraint;
 import javax.validation.Payload;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
-
-import org.apache.bval.constraints.NotEmpty;
-
 import java.lang.annotation.Retention;
+
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 /**
@@ -41,7 +39,7 @@ import static java.lang.annotation.Reten
 @Constraint(validatedBy = {})
 // test that Password is validated although only combined constraints exists, no own implementation 
 public @interface Password {
-    String[] groups() default {};
+    Class<?>[] groups() default {};
 
     String message() default "Wrong password";
 

Modified: incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/jsr303/ConstraintDefinitionsTest.java
URL: http://svn.apache.org/viewvc/incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/jsr303/ConstraintDefinitionsTest.java?rev=941207&r1=941206&r2=941207&view=diff
==============================================================================
--- incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/jsr303/ConstraintDefinitionsTest.java (original)
+++ incubator/bval/trunk/bval-jsr303/src/test/java/org/apache/bval/jsr303/ConstraintDefinitionsTest.java Wed May  5 08:58:58 2010
@@ -20,10 +20,9 @@ package org.apache.bval.jsr303;
 
 import junit.framework.Assert;
 import junit.framework.TestCase;
+import org.apache.bval.constraints.NotNullValidator;
 
-import javax.validation.Validation;
-import javax.validation.Validator;
-import javax.validation.ValidatorFactory;
+import javax.validation.*;
 import javax.validation.constraints.Min;
 import javax.validation.metadata.BeanDescriptor;
 import javax.validation.metadata.ConstraintDescriptor;
@@ -71,6 +70,99 @@ public class ConstraintDefinitionsTest e
         }
     }
 
+    /**
+     * Checks that a {@link ConstraintDefinitionException} is thrown when
+     * parsing a constraint definition with no <code>groups()</code> method.
+     */
+    public void testNoGroupsConstraint() {
+        try {
+            getValidator().validate(new NoGroups());
+            fail("No exception thrown when parsing a constraint definition with no groups() method");
+        } catch (ConstraintDefinitionException e) {
+            // correct
+        }
+    }
+
+    /**
+     * Checks that a {@link ConstraintDefinitionException} is thrown when
+     * parsing a constraint definition with an invalid <code>groups()</code>
+     * method.
+     */
+    public void testInvalidDefaultGroupsConstraint() {
+        try {
+            getValidator().validate(new InvalidGroups());
+            fail("No exception thrown when parsing a constraint definition with a groups() method does not return Class[]");
+        } catch (ConstraintDefinitionException e) {
+            // correct
+        }
+    }
+
+    /**
+     * Checks that a {@link ConstraintDefinitionException} is thrown when
+     * parsing a constraint definition with no <code>payload()</code> method.
+     */
+    public void testNoPayloadConstraint() {
+        try {
+            getValidator().validate(new NoPayload());
+            fail("No exception thrown when parsing a constraint definition with no payload() method");
+        } catch (ConstraintDefinitionException e) {
+            // correct
+        }
+    }
+    
+    /**
+     * Checks that a {@link ConstraintDefinitionException} is thrown when
+     * parsing a constraint definition with an invalid <code>payload()</code>
+     * method.
+     */
+    public void testInvalidDefaultPayloadConstraint() {
+        try {
+            getValidator().validate(new InvalidPayload());
+            fail("No exception thrown when parsing a constraint definition with a payload() method does not return an empty array");
+        } catch (ConstraintDefinitionException e) {
+            // correct
+        }
+    }
+
+    /**
+     * Checks that a {@link ConstraintDefinitionException} is thrown when
+     * parsing a constraint definition with no <code>message()</code> method.
+     */
+    public void testNoMessageConstraint() {
+        try {
+            getValidator().validate(new NoMessage());
+            fail("No exception thrown when parsing a constraint definition with no payload() method");
+        } catch (ConstraintDefinitionException e) {
+            // correct
+        }
+    }
+    
+    /**
+     * Checks that a {@link ConstraintDefinitionException} is thrown when
+     * parsing a constraint definition with an invalid <code>message()</code>
+     * method.
+     */
+    public void testInvalidDefaultMessageConstraint() {
+        try {
+            getValidator().validate(new InvalidMessage());
+            fail("No exception thrown when parsing a constraint definition with a message() method does not return a String");
+        } catch (ConstraintDefinitionException e) {
+            // correct
+        }
+    }
+
+    /**
+     * Checks that a {@link ConstraintDefinitionException} is thrown when
+     * parsing a constraint definition with a method starting with 'valid'.
+     */
+    public void testInvalidAttributeConstraint() {
+        try {
+            getValidator().validate(new InvalidAttribute());
+            fail("No exception thrown when parsing a constraint definition with a method starting with 'valid'");
+        } catch (ConstraintDefinitionException e) {
+            // correct
+        }
+    }
     
     public static class Person {
         @MinList({
@@ -80,11 +172,115 @@ public class ConstraintDefinitionsTest e
         public Integer age;
     }
     
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    public static @interface MinList {
+        Min[] value();
+    }
+    
+    public static class NoGroups {
+        @NoGroupsConstraint
+        public String prop;
+    }
+    
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    @Constraint(validatedBy = {NotNullValidator.class})
+    public static @interface NoGroupsConstraint {
+        String message() default "def msg";
+        Class<? extends Payload>[] payload() default {};
+    }
+    
+    public static class InvalidGroups {
+        @InvalidGroupsConstraint
+        public String prop;
+    }
+    
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    @Constraint(validatedBy = {NotNullValidator.class})
+    public static @interface InvalidGroupsConstraint {
+        String message() default "def msg";
+        String[] groups() default { "Group1" };
+        Class<? extends Payload>[] payload() default {};
+    }
+    
+    public static class NoPayload {
+        @NoPayloadConstraint
+        public String prop;
+    }
+    
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    @Constraint(validatedBy = {NotNullValidator.class})
+    public static @interface NoPayloadConstraint {
+        String message() default "def msg";
+        String[] groups() default {};
+    }
+    
+    public static class InvalidPayload {
+        @InvalidPayloadConstraint
+        public String prop;
+    }
+    
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    @Constraint(validatedBy = {NotNullValidator.class})
+    public static @interface InvalidPayloadConstraint {
+        String message() default "def msg";
+        String[] groups() default {};
+        Class<? extends Payload>[] payload() default {Payload1.class};
+        public static class Payload1 implements Payload {
+        }
+    }
+    
+    public static class NoMessage {
+        @NoMessageConstraint
+        public String prop;
+    }
+    
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    @Constraint(validatedBy = {NotNullValidator.class})
+    public static @interface NoMessageConstraint {
+        String[] groups() default {};
+        Class<? extends Payload>[] payload() default {};
+    }
+    
+    public static class InvalidMessage {
+        @InvalidMessageConstraint(message=2)
+        public String prop;
+    }
+    
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    @Constraint(validatedBy = {NotNullValidator.class})
+    public static @interface InvalidMessageConstraint {
+        int message();
+        String[] groups() default {};
+        Class<? extends Payload>[] payload() default {};
+    }
+    
+    public static class InvalidAttribute {
+        @InvalidAttributeConstraint
+        public String prop;
+    }
+    
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE })
+    @Retention(RUNTIME)
+    @Documented
+    @Constraint(validatedBy = {NotNullValidator.class})
+    public static @interface InvalidAttributeConstraint {
+        String message() default "def msg";
+        String[] groups() default {};
+        Class<? extends Payload>[] payload() default {};
+        String validValue() default "1";
+    }
 }
-
-@Target({ METHOD, FIELD, ANNOTATION_TYPE })
-@Retention(RUNTIME)
-@Documented
-@interface MinList {
-    Min[] value();
-}
\ No newline at end of file