Support annotations on parameterized types 35/116635/2
authorJim Hahn <jrh3@att.com>
Wed, 6 Jan 2021 16:15:45 +0000 (11:15 -0500)
committerJim Hahn <jrh3@att.com>
Wed, 6 Jan 2021 18:04:34 +0000 (13:04 -0500)
It appears that java.validation allows validation annotations to be used
on the type parameters of Collection and Map classes.  Updated the
validation code to support that.  Once policy-models has been updated to
use this approach, the original @Items and @Entries annotations will be
deprecated.

Issue-ID: POLICY-2648
Change-Id: Ic953be485ebafc9869f72407518f6549585353c9
Signed-off-by: Jim Hahn <jrh3@att.com>
13 files changed:
common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java
common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java
common-parameters/src/main/java/org/onap/policy/common/parameters/Item2Validator.java [new file with mode: 0644]
common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Max.java
common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Min.java
common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/NotBlank.java
common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/NotNull.java
common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Pattern.java
common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Valid.java
common-parameters/src/test/java/org/onap/policy/common/parameters/TestBeanValidator.java
common-parameters/src/test/java/org/onap/policy/common/parameters/TestFieldValidator.java
common-parameters/src/test/java/org/onap/policy/common/parameters/TestItem2Validator.java [new file with mode: 0644]
common-parameters/src/test/java/org/onap/policy/common/parameters/ValidatorUtil.java

index 51b1140..6791c61 100644 (file)
@@ -288,6 +288,9 @@ public class BeanValidator {
         }
 
         ItemValidator itemValidator = makeItemValidator(annot);
+        if (itemValidator.isEmpty()) {
+            return true;
+        }
 
         return verCollection(result, fieldName, itemValidator, value);
     }
@@ -304,7 +307,7 @@ public class BeanValidator {
     public boolean verCollection(BeanValidationResult result, String fieldName, ValueValidator itemValidator,
                     Object value) {
 
-        if (!(value instanceof Collection) || itemValidator.isEmpty()) {
+        if (!(value instanceof Collection)) {
             return true;
         }
 
@@ -375,6 +378,62 @@ public class BeanValidator {
         return false;
     }
 
+    /**
+     * Validates the items in a Map.
+     *
+     * @param result where to add the validation result
+     * @param fieldName name of the field containing the map
+     * @param keyValidator validator for an individual key within the Map entry
+     * @param valueValidator validator for an individual value within the Map entry
+     * @param value value to be verified
+     * @return {@code true} if the next check should be performed, {@code false} otherwise
+     */
+    public boolean verMap(BeanValidationResult result, String fieldName, ValueValidator keyValidator,
+                    ValueValidator valueValidator, Object value) {
+
+        if (!(value instanceof Map)) {
+            return true;
+        }
+
+        Map<?, ?> map = (Map<?, ?>) value;
+
+        BeanValidationResult result2 = new BeanValidationResult(fieldName, value);
+
+        for (Entry<?, ?> entry : map.entrySet()) {
+            String name = getEntryName(entry);
+
+            BeanValidationResult result3 = new BeanValidationResult(name, entry);
+            keyValidator.validateValue(result3, "key", entry.getKey());
+            valueValidator.validateValue(result3, "value", entry.getValue());
+
+            if (!result3.isClean()) {
+                result2.addResult(result3);
+            }
+        }
+
+        if (result2.isClean()) {
+            return true;
+        }
+
+        result.addResult(result2);
+        return false;
+    }
+
+    /**
+     * Gets a name for an entry.
+     *
+     * @param entry entry whose name is to be determined
+     * @return a name for the entry
+     */
+    protected <K, V> String getEntryName(Map.Entry<K, V> entry) {
+        K key = entry.getKey();
+        if (key == null) {
+            return "";
+        }
+
+        return key.toString();
+    }
+
     /**
      * Makes a field validator.
      *
index 249185c..58f3e83 100644 (file)
 package org.onap.policy.common.parameters;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Map;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
@@ -76,6 +80,8 @@ public class FieldValidator extends ValueValidator {
         }
 
         validator.addValidators(this);
+        addListValidator(validator);
+        addMapValidator(validator);
 
         if (checkers.isEmpty()) {
             // has no annotations - nothing to check
@@ -104,6 +110,65 @@ public class FieldValidator extends ValueValidator {
         }
     }
 
+    /**
+     * Adds validators for the individual items within a collection, if the field is a
+     * collection.
+     *
+     * @param validator provider of validation methods
+     */
+    private void addListValidator(BeanValidator validator) {
+        if (!Collection.class.isAssignableFrom(field.getType())) {
+            return;
+        }
+
+        AnnotatedType tannot = field.getAnnotatedType();
+        if (!(tannot instanceof AnnotatedParameterizedType)) {
+            return;
+        }
+
+        AnnotatedType[] targs = ((AnnotatedParameterizedType) tannot).getAnnotatedActualTypeArguments();
+        if (targs.length != 1) {
+            return;
+        }
+
+        Item2Validator itemValidator = new Item2Validator(validator, targs[0]);
+        if (itemValidator.isEmpty()) {
+            return;
+        }
+
+        checkers.add((result, fieldName, value) -> validator.verCollection(result, fieldName, itemValidator, value));
+    }
+
+    /**
+     * Adds validators for the individual entries within a map, if the field is a map.
+     *
+     * @param validator provider of validation methods
+     */
+    private void addMapValidator(BeanValidator validator) {
+        if (!Map.class.isAssignableFrom(field.getType())) {
+            return;
+        }
+
+        AnnotatedType tannot = field.getAnnotatedType();
+        if (!(tannot instanceof AnnotatedParameterizedType)) {
+            return;
+        }
+
+        AnnotatedType[] targs = ((AnnotatedParameterizedType) tannot).getAnnotatedActualTypeArguments();
+        if (targs.length != 2) {
+            return;
+        }
+
+        Item2Validator keyValidator = new Item2Validator(validator, targs[0]);
+        Item2Validator valueValidator = new Item2Validator(validator, targs[1]);
+        if (keyValidator.isEmpty() && valueValidator.isEmpty()) {
+            return;
+        }
+
+        checkers.add((result, fieldName, value) -> validator.verMap(result, fieldName, keyValidator, valueValidator,
+                        value));
+    }
+
     /**
      * Performs validation of a single field.
      *
diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/Item2Validator.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/Item2Validator.java
new file mode 100644 (file)
index 0000000..c82244d
--- /dev/null
@@ -0,0 +1,71 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.parameters;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedType;
+
+/**
+ * Validator of an "item", which is typically found in a collection, or the key or value
+ * components of an entry in a Map.
+ */
+public class Item2Validator extends ValueValidator {
+    private final AnnotatedType annotatedType;
+
+    /**
+     * Constructs the object.
+     *
+     * @param validator provider of validation methods
+     * @param annotatedType a type having validation annotations to be
+     *        applied to the item
+     */
+    public Item2Validator(BeanValidator validator, AnnotatedType annotatedType) {
+        this(validator, annotatedType, true);
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param validator provider of validation methods
+     * @param annotatedType a type having validation annotations to be
+     *        applied to the item
+     * @param addValidators {@code true} if to add validators
+     */
+    public Item2Validator(BeanValidator validator, AnnotatedType annotatedType, boolean addValidators) {
+        this.annotatedType = annotatedType;
+
+        if (addValidators) {
+            validator.addValidators(this);
+        }
+    }
+
+    /**
+     * Gets an annotation from the field or the class.
+     *
+     * @param annotClass annotation class of interest
+     * @return the annotation, or {@code null} if the {@link #annotatedType} does
+     *         not contain the desired annotation
+     */
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> annotClass) {
+        return annotatedType.getAnnotation(annotClass);
+    }
+}
index 1d8fd90..f28fd2c 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 package org.onap.policy.common.parameters.annotations;
 
 import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.TYPE_USE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 @Retention(RUNTIME)
-@Target(FIELD)
+@Target({FIELD, TYPE_USE})
 public @interface Max {
 
     /**
index 9ad6d7e..305d981 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 package org.onap.policy.common.parameters.annotations;
 
 import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.TYPE_USE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 @Retention(RUNTIME)
-@Target(FIELD)
+@Target({FIELD, TYPE_USE})
 public @interface Min {
 
     /**
index 169fa59..0744beb 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ package org.onap.policy.common.parameters.annotations;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.ElementType.TYPE_USE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Retention;
@@ -31,7 +32,7 @@ import java.lang.annotation.Target;
  * Indicates that a field (i.e., String) may not be empty.
  */
 @Retention(RUNTIME)
-@Target({TYPE, FIELD})
+@Target({TYPE, FIELD, TYPE_USE})
 public @interface NotBlank {
 
 }
index 3c7bc8b..8135600 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ package org.onap.policy.common.parameters.annotations;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.ElementType.TYPE_USE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Retention;
@@ -31,7 +32,7 @@ import java.lang.annotation.Target;
  * Indicates that a field may not be null.
  */
 @Retention(RUNTIME)
-@Target({TYPE, FIELD})
+@Target({TYPE, FIELD, TYPE_USE})
 public @interface NotNull {
 
 }
index a30d814..91a74d7 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 package org.onap.policy.common.parameters.annotations;
 
 import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.TYPE_USE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 @Retention(RUNTIME)
-@Target(FIELD)
+@Target({FIELD, TYPE_USE})
 public @interface Pattern {
 
     /**
index a3a1d59..227a726 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,13 +22,14 @@ package org.onap.policy.common.parameters.annotations;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.ElementType.TYPE_USE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 @Retention(RUNTIME)
-@Target({TYPE, FIELD})
+@Target({TYPE, FIELD, TYPE_USE})
 public @interface Valid {
 
 }
index 5d53926..00ed972 100644 (file)
@@ -23,6 +23,7 @@ package org.onap.policy.common.parameters;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -30,7 +31,6 @@ import java.util.function.Consumer;
 import lombok.Getter;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.common.parameters.annotations.Entries;
 import org.onap.policy.common.parameters.annotations.Items;
 import org.onap.policy.common.parameters.annotations.Max;
 import org.onap.policy.common.parameters.annotations.Min;
@@ -489,11 +489,10 @@ public class TestBeanValidator {
     public void testVerMap() {
         @Getter
         class Container {
-            @Entries(key = @Items(), value = @Items(min = {@Min(5)}))
-            Map<String, Integer> items;
+            Map<String, @Min(5) Integer> items;
 
-            // not a map - should not be checked
-            @Entries(key = @Items(), value = @Items(min = {@Min(5)}))
+            // not a map
+            @NotBlank
             String strValue;
 
             String noAnnotations;
@@ -521,6 +520,26 @@ public class TestBeanValidator {
         assertTrue(validator.validateTop(TOP, cont).isValid());
     }
 
+    @Test
+    public void testGetEntryName() {
+        assertThat(validator.getEntryName(makeEntry(null, 0))).isEmpty();
+        assertThat(validator.getEntryName(makeEntry("", 0))).isEmpty();
+        assertThat(validator.getEntryName(makeEntry(STRING_VALUE, 0))).isEqualTo(STRING_VALUE);
+    }
+
+    /**
+     * Makes a Map entry with the given key and value.
+     *
+     * @param key desired key
+     * @param value desired value
+     * @return a new Map entry
+     */
+    private Map.Entry<String, Integer> makeEntry(String key, int value) {
+        HashMap<String, Integer> map = new HashMap<>();
+        map.put(key, value);
+        return map.entrySet().iterator().next();
+    }
+
     private <T> void assertNumeric(String testName, T object, Consumer<Integer> setter, String fieldName,
                     String expectedText, int inside, int edge, int outside) {
         setter.accept(inside);
index e4432c8..29b4b0e 100644 (file)
@@ -25,13 +25,18 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.Map;
 import lombok.Getter;
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.common.parameters.annotations.NotBlank;
 import org.onap.policy.common.parameters.annotations.NotNull;
 
 public class TestFieldValidator extends ValidatorUtil {
+    private static final String INT_LIST_FIELD = "intList";
+    private static final String INT_MAP_FIELD = "intMap";
     private static final String UNANNOTATED_FIELD = "unannotated";
     private static final String INT_FIELD = "intValue";
     private static final int VALID_INT = 10;
@@ -44,6 +49,24 @@ public class TestFieldValidator extends ValidatorUtil {
     @Getter
     private int intValue;
 
+    @Getter
+    private List<@Min(1) Integer> intList;
+
+    @Getter
+    private Map<@NotBlank String, @Min(1) Integer> intMap;
+
+    @Getter
+    private Map<@NotBlank String, Integer> annotatedKeyMap;
+
+    @Getter
+    private Map<String, @Min(1) Integer> annotatedValueMap;
+
+    @Getter
+    private List<Integer> unannotatedList;
+
+    @Getter
+    private Map<String, Integer> unannotatedMap;
+
     @NotNull
     @Getter
     private boolean boolValue;
@@ -152,6 +175,52 @@ public class TestFieldValidator extends ValidatorUtil {
                         .isFalse();
     }
 
+    @Test
+    public void testAddListValidator() {
+
+        // unannotated
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("unannotatedList")).isEmpty()).isTrue();
+
+        // annotated
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_LIST_FIELD)).isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testAddMapValidator() {
+
+        // unannotated
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("unannotatedMap")).isEmpty()).isTrue();
+
+        // annotated
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_MAP_FIELD)).isEmpty()).isFalse();
+
+        // only the key is annotated
+        FieldValidator validator = new FieldValidator(bean, TestFieldValidator.class, getField("annotatedKeyMap"));
+        assertThat(validator.isEmpty()).isFalse();
+
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        annotatedKeyMap = Map.of("abc", -10);
+        validator.validateField(result, this);
+        assertThat(result.getResult()).isNull();
+
+        annotatedKeyMap = Map.of(" ", -10);
+        validator.validateField(result, this);
+        assertThat(result.getResult()).contains("blank").doesNotContain("-10");
+
+        // only the value is annotated
+        validator = new FieldValidator(bean, TestFieldValidator.class, getField("annotatedValueMap"));
+        assertThat(validator.isEmpty()).isFalse();
+
+        result = new BeanValidationResult(MY_NAME, this);
+        annotatedValueMap = Map.of(" ", 10);
+        validator.validateField(result, this);
+        assertThat(result.getResult()).isNull();
+
+        annotatedValueMap = Map.of(" ", -10);
+        validator.validateField(result, this);
+        assertThat(result.getResult()).doesNotContain("blank").contains("\" \"", "-10");
+    }
+
     @Test
     public void testValidateField_testGetValue() {
         // unannotated
@@ -179,6 +248,38 @@ public class TestFieldValidator extends ValidatorUtil {
                         .hasMessage("expected exception");
     }
 
+    @Test
+    public void testValidateField_testGetValue_ListField() {
+        // valid
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        intList = List.of(10, 20, 30, 40);
+        new FieldValidator(bean, getClass(), getField(INT_LIST_FIELD)).validateField(result, this);
+        assertThat(result.getResult()).isNull();
+
+        // invalid
+        result = new BeanValidationResult(MY_NAME, this);
+        intList = List.of(9, -8, 7, -6);
+        new FieldValidator(bean, getClass(), getField(INT_LIST_FIELD)).validateField(result, this);
+        assertThat(result.getResult()).doesNotContain("0", "9").contains("1", "-8").doesNotContain("2", "7")
+                        .contains("3", "-6");
+    }
+
+    @Test
+    public void testValidateField_testGetValue_MapField() {
+        // valid
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        intMap = Map.of("ten", 10, "twenty", 20, "thirty", 30, "forty", 40);
+        new FieldValidator(bean, getClass(), getField(INT_MAP_FIELD)).validateField(result, this);
+        assertThat(result.getResult()).isNull();
+
+        // invalid
+        result = new BeanValidationResult(MY_NAME, this);
+        intMap = Map.of("ten", 9, "twenty", -8, "thirty", 7, "forty", -6);
+        new FieldValidator(bean, getClass(), getField(INT_MAP_FIELD)).validateField(result, this);
+        assertThat(result.getResult()).doesNotContain("ten", "9").contains("twenty", "-8").doesNotContain("thirty", "7")
+                        .contains("forty", "-6");
+    }
+
     @Test
     public void testClassOnly() {
         // class-level annotation has no bearing on a static field
diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestItem2Validator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestItem2Validator.java
new file mode 100644 (file)
index 0000000..f8d3864
--- /dev/null
@@ -0,0 +1,121 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.parameters;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+public class TestItem2Validator extends ValidatorUtil {
+
+    // annotated fields - each field must have exactly one annotation
+
+    /**
+     * This annotation does not contain a method returning an array.
+     */
+    @Min(value = 0)
+    private int notArray;
+
+    /**
+     * This annotation doesn't contain any annotations that the {@link BeanValidator}
+     * recognizes.
+     */
+    @Simple
+    private int mismatch;
+
+    /**
+     * No annotations.
+     */
+    @SuppressWarnings("unused")
+    private int noAnnotations;
+
+    /**
+     * One matching annotation.
+     */
+    @NotNull
+    private int match;
+
+    /**
+     * Multiple matching annotations.
+     */
+    @NotNull
+    @NotBlank
+    private String multiMatch;
+
+
+    @Before
+    public void setUp() {
+        bean = new BeanValidator();
+    }
+
+    @Test
+    public void testGetAnnotation() {
+        // no matches
+        assertThat(new Item2Validator(bean, getAnnotType("noAnnotations"), true).isEmpty()).isTrue();
+
+        // had a match
+        assertThat(new Item2Validator(bean, getAnnotType("match"), true).checkers).hasSize(1);
+
+        // multiple matches
+        Item2Validator validator = new Item2Validator(bean, getAnnotType("multiMatch"), true);
+        assertThat(validator.checkers).hasSize(2);
+
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        validator.validateValue(result, MY_FIELD, HELLO);
+        assertThat(result.getResult()).isNull();
+
+        result = new BeanValidationResult(MY_NAME, this);
+        validator.validateValue(result, MY_FIELD, null);
+        assertThat(result.getResult()).isNotNull();
+
+        result = new BeanValidationResult(MY_NAME, this);
+        validator.validateValue(result, MY_FIELD, "");
+        assertThat(result.getResult()).isNotNull();
+    }
+
+    @Test
+    public void testItem2ValidatorBeanValidatorAnnotation() {
+        assertThat(new Item2Validator(bean, getAnnotType("match")).isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testItem2ValidatorBeanValidatorAnnotationBoolean() {
+        assertThat(new Item2Validator(bean, getAnnotType("match"), true).isEmpty()).isFalse();
+
+        assertThat(new Item2Validator(bean, getAnnotType("match"), false).isEmpty()).isTrue();
+    }
+
+    // these annotations are not recognized by the BeanValidator
+
+    @Retention(RUNTIME)
+    @Target(FIELD)
+    public @interface Simple {
+
+    }
+}
index 4df014f..e39b5b8 100644 (file)
@@ -21,6 +21,7 @@
 package org.onap.policy.common.parameters;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedType;
 import java.lang.reflect.Field;
 
 /**
@@ -43,6 +44,16 @@ public class ValidatorUtil {
         return getField(fieldName).getAnnotations()[0];
     }
 
+    /**
+     * Gets the annotated type for a given field.
+     *
+     * @param fieldName name of the field of interest
+     * @return the given field's annotated type
+     */
+    protected AnnotatedType getAnnotType(String fieldName) {
+        return getField(fieldName).getAnnotatedType();
+    }
+
     /**
      * Gets a field from this object.
      *