diff options
Diffstat (limited to 'common-parameters')
11 files changed, 946 insertions, 284 deletions
diff --git a/common-parameters/pom.xml b/common-parameters/pom.xml index c0efcf70..02765df4 100644 --- a/common-parameters/pom.xml +++ b/common-parameters/pom.xml @@ -1,7 +1,7 @@ <!-- ============LICENSE_START======================================================= Copyright (C) 2018 Ericsson. All rights reserved. - Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + Modifications 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. @@ -40,6 +40,16 @@ <artifactId>commons-lang3</artifactId> </dependency> <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java index 3f5abccc..51b11402 100644 --- a/common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java +++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java @@ -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. @@ -34,11 +34,14 @@ import org.onap.policy.common.parameters.annotations.NotBlank; import org.onap.policy.common.parameters.annotations.NotNull; import org.onap.policy.common.parameters.annotations.Pattern; import org.onap.policy.common.parameters.annotations.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Bean validator, supporting the parameter annotations. */ public class BeanValidator { + public static final Logger logger = LoggerFactory.getLogger(BeanValidator.class); /** * Validates top level fields within an object. For each annotated field, it retrieves @@ -147,18 +150,18 @@ public class BeanValidator { */ public boolean verRegex(BeanValidationResult result, String fieldName, Pattern annot, Object value) { try { - if (value instanceof String && !com.google.re2j.Pattern.matches(annot.regexp(), value.toString())) { - ObjectValidationResult result2 = new ObjectValidationResult(fieldName, xlate(value), - ValidationStatus.INVALID, "does not match regular expression " + annot.regexp()); - result.addResult(result2); - return false; + if (value instanceof String && com.google.re2j.Pattern.matches(annot.regexp(), value.toString())) { + return true; } + } catch (RuntimeException e) { - // TODO log at trace level - return true; + logger.warn("validation error for regular expression: {}", annot.regexp(), e); } - return true; + ObjectValidationResult result2 = new ObjectValidationResult(fieldName, xlate(value), ValidationStatus.INVALID, + "does not match regular expression " + annot.regexp()); + result.addResult(result2); + return false; } /** @@ -352,7 +355,7 @@ public class BeanValidator { */ public boolean verMap(BeanValidationResult result, String fieldName, EntryValidator entryValidator, Object value) { - if (!(value instanceof Map)) { + if (!(value instanceof Map) || entryValidator.isEmpty()) { return true; } diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java index e762dc0e..249185c7 100644 --- a/common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java +++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java @@ -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. @@ -157,6 +157,7 @@ public class FieldValidator extends ValueValidator { * @return the annotation, or {@code null} if neither the field nor the class has the * desired annotation */ + @Override public <T extends Annotation> T getAnnotation(Class<T> annotClass) { // field annotation takes precedence over class annotation @@ -178,9 +179,9 @@ public class FieldValidator extends ValueValidator { */ private Method getAccessor(Class<?> clazz, String fieldName) { String capname = StringUtils.capitalize(fieldName); - Method accessor = getMethod(clazz, "get" + capname); - if (accessor != null) { - return accessor; + Method accessor2 = getMethod(clazz, "get" + capname); + if (accessor2 != null) { + return accessor2; } return getMethod(clazz, "is" + capname); diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/ItemValidator.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/ItemValidator.java index d0c027c1..07efebbe 100644 --- a/common-parameters/src/main/java/org/onap/policy/common/parameters/ItemValidator.java +++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/ItemValidator.java @@ -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. @@ -65,6 +65,7 @@ public class ItemValidator extends ValueValidator { * @return the annotation, or {@code null} if the {@link #annotationContainer} does * not contain the desired annotation */ + @Override public <T extends Annotation> T getAnnotation(Class<T> annotClass) { try { for (Method meth : annotationContainer.getClass().getDeclaredMethods()) { @@ -80,7 +81,10 @@ public class ItemValidator extends ValueValidator { return null; } - private <T extends Annotation> T getAnnotation2(Class<T> annotClass, Method method) + /** + * Note: this is only marked "protected" so it can be overridden for junit testing. + */ + protected <T extends Annotation> T getAnnotation2(Class<T> annotClass, Method method) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class<?> ret = method.getReturnType(); @@ -101,7 +105,9 @@ public class ItemValidator extends ValueValidator { return null; } - // TODO log if there's more than one item + if (arrobj.length > 1) { + throw new IllegalArgumentException("extra item annotations of type: " + annotClass.getName()); + } return arrobj[0]; } diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/ValueValidator.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/ValueValidator.java index 9095bfd0..6a641a17 100644 --- a/common-parameters/src/main/java/org/onap/policy/common/parameters/ValueValidator.java +++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/ValueValidator.java @@ -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. @@ -37,12 +37,6 @@ import org.onap.policy.common.parameters.annotations.NotNull; public class ValueValidator { /** - * {@code True} if there is a field-level annotation, {@code false} otherwise. - */ - @Setter(AccessLevel.PROTECTED) - private boolean fieldIsAnnotated = false; - - /** * {@code True} if the value is allowed to be {@code null}, {@code false} otherwise. * Subclasses are expected to set this, typically based on the validation annotations * associated with the value. diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestBeanValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestBeanValidator.java index f1e468b0..5d539260 100644 --- a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestBeanValidator.java +++ b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestBeanValidator.java @@ -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. @@ -20,30 +20,30 @@ package org.onap.policy.common.parameters; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; +import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import lombok.AccessLevel; 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; import org.onap.policy.common.parameters.annotations.NotBlank; import org.onap.policy.common.parameters.annotations.NotNull; +import org.onap.policy.common.parameters.annotations.Pattern; +import org.onap.policy.common.parameters.annotations.Valid; public class TestBeanValidator { - private static final String GET_MSG = "\"get\""; - private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception"); private static final String TOP = "top"; private static final String STR_FIELD = "strValue"; private static final String INT_FIELD = "intValue"; private static final String NUM_FIELD = "numValue"; - private static final String BOOL_FIELD = "boolValue"; private static final String STRING_VALUE = "string value"; private static final int INT_VALUE = 20; @@ -114,115 +114,6 @@ public class TestBeanValidator { } @Test - public void testValidateField() { - /* - * Note: nested classes contain fields like "$this", thus the check for "$" in the - * variable name is already covered by the other tests. - */ - - /* - * Class with no annotations. - */ - class NoAnnotations { - @SuppressWarnings("unused") - String strValue; - } - - NoAnnotations noAnnot = new NoAnnotations(); - noAnnot.strValue = null; - assertTrue(validator.validateTop(TOP, noAnnot).isValid()); - - /* - * Class containing a static field with an annotation. - */ - AnnotFieldStatic annotFieldStatic = new AnnotFieldStatic(); - assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, annotFieldStatic)) - .withMessageContaining(STR_FIELD).withMessageContaining("static"); - - /* - * Class containing a static field, with an annotation at the class level. - */ - AnnotClassStatic annotClassStatic = new AnnotClassStatic(); - assertTrue(validator.validateTop(TOP, annotClassStatic).isValid()); - - /* - * Class with no getter method, with field-level annotation. - */ - class NoGetter { - @NotNull - String strValue; - } - - NoGetter noGetter = new NoGetter(); - assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, noGetter)) - .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG); - - /* - * Class with no getter method, with class-level annotation. - */ - @NotNull - class ClassNoGetter { - @SuppressWarnings("unused") - String strValue; - } - - ClassNoGetter classNoGetter = new ClassNoGetter(); - assertTrue(validator.validateTop(TOP, classNoGetter).isValid()); - - /* - * Class with "blank", but no "null" check. Value is null. - */ - class NoNullCheck { - @NotBlank - @Getter - String strValue; - } - - NoNullCheck noNullCheck = new NoNullCheck(); - assertTrue(validator.validateTop(TOP, noNullCheck).isValid()); - - /* - * Class with conflicting minimum and maximum, where the value doesn't satisfy - * either of them. This should only generate one result, rather than one for each - * check. Note: the "max" check occurs before the "min" check, so that's the one - * we expect in the result. - */ - class MinAndMax { - @Getter - @Min(200) - @Max(100) - Integer intValue; - } - - MinAndMax minAndMax = new MinAndMax(); - minAndMax.intValue = 150; - BeanValidationResult result = validator.validateTop(INT_FIELD, minAndMax); - assertFalse(result.isValid()); - assertInvalid("testValidateField", result, INT_FIELD, "maximum"); - assertFalse(result.getResult().contains("minimum")); - } - - @Test - public void testGetValue() { - /* - * Class where the getter throws an exception. - */ - class GetExcept { - @NotNull - String strValue; - - @SuppressWarnings("unused") - public String getStrValue() { - throw EXPECTED_EXCEPTION; - } - } - - GetExcept getExcept = new GetExcept(); - assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, getExcept)) - .withMessageContaining(STR_FIELD).withMessageContaining("accessor threw"); - } - - @Test public void testVerNotNull() { class NotNullCheck { @Getter @@ -281,6 +172,58 @@ public class TestBeanValidator { } @Test + public void testVerRegex() { + class RegexCheck { + @Getter + @Pattern(regexp = "[a-f]*") + String strValue; + } + + RegexCheck regexCheck = new RegexCheck(); + + // does not match + regexCheck.strValue = "xyz"; + assertInvalid("testVerRegex", validator.validateTop(TOP, regexCheck), STR_FIELD, + "does not match regular expression [a-f]"); + + // matches + regexCheck.strValue = "abcabc"; + assertTrue(validator.validateTop(TOP, regexCheck).isValid()); + + // invalid regex + class InvalidRegexCheck { + @Getter + @Pattern(regexp = "[a-f") + String strValue; + } + + InvalidRegexCheck invalidRegexCheck = new InvalidRegexCheck(); + + // does not match + invalidRegexCheck.strValue = "abc"; + assertInvalid("testVerRegex", validator.validateTop(TOP, invalidRegexCheck), STR_FIELD, + "does not match regular expression [a-f"); + + // matches + regexCheck.strValue = "abcabc"; + assertTrue(validator.validateTop(TOP, regexCheck).isValid()); + + /* + * Class with "regex" annotation on an integer. + */ + class RegexInt { + @Getter + @Pattern(regexp = "[a-f]*") + int intValue; + } + + RegexInt regexInt = new RegexInt(); + regexInt.intValue = 0; + assertInvalid("testVerRegex", validator.validateTop(TOP, regexInt), INT_FIELD, + "does not match regular expression [a-f]"); + } + + @Test public void testVerMax() { /* * Field is not a number. @@ -468,184 +411,132 @@ public class TestBeanValidator { assertTrue(validator.validateTop(TOP, atomIntField).isValid()); } - private <T> void assertNumeric(String testName, T object, Consumer<Integer> setter, String fieldName, - String expectedText, int inside, int edge, int outside) { - setter.accept(inside); - assertTrue(validator.validateTop(TOP, object).isValid()); - - // on the edge - setter.accept(edge); - assertTrue(validator.validateTop(TOP, object).isValid()); + @Test + public void testVerCascade() { + class Item { + @Getter + @NotNull + Integer intValue; + } - // invalid - setter.accept(outside); - assertInvalid("testVerNotNull", validator.validateTop(TOP, object), fieldName, expectedText); - } + @Getter + class Container { + @Valid + Item checked; - @Test - public void testGetAccessor() { - /* - * Class with "get" method has been tested through-out this junit, so no need to - * do more. - */ + Item unchecked; - /* - * Class with "is" method. - */ - class IsField { - @NotNull - Boolean boolValue; + @Valid + List<Item> items; - @SuppressWarnings("unused") - public Boolean isBoolValue() { - return boolValue; - } + @Valid + Map<String, Item> itemMap; } - // ok value - IsField isField = new IsField(); - isField.boolValue = true; - assertTrue(validator.validateTop(TOP, isField).isValid()); + Container cont = new Container(); + cont.unchecked = new Item(); + cont.items = List.of(new Item()); + cont.itemMap = Map.of(STRING_VALUE, new Item()); - // invalid value - isField.boolValue = null; - assertInvalid("testGetAccessor", validator.validateTop(TOP, isField), BOOL_FIELD, "null"); - } + cont.checked = null; + assertTrue(validator.validateTop(TOP, cont).isValid()); - @Test - public void testGetMethod() { - /* - * Class with some fields annotated and some not. - */ - @Getter - class Mixed { - Integer intValue; + cont.checked = new Item(); - @NotNull - String strValue; - } + assertInvalid("testVerCascade", validator.validateTop(TOP, cont), INT_FIELD, "null"); - // invalid - Mixed mixed = new Mixed(); - BeanValidationResult result = validator.validateTop(TOP, mixed); - assertInvalid("testGetMethod", result, STR_FIELD, "null"); - assertFalse(result.getResult().contains(INT_FIELD)); - - // intValue is null, but it isn't annotated so this should be valid - mixed.strValue = STRING_VALUE; - assertTrue(validator.validateTop(TOP, mixed).isValid()); + cont.checked.intValue = INT_VALUE; + assertTrue(validator.validateTop(TOP, cont).isValid()); } @Test - public void testValidMethod() { + public void testVerCollection() { + @Getter + class Container { + @Items(min = @Min(5)) + List<Integer> items; - /* - * Plain getter. - */ - class PlainGetter { - @NotNull - @Getter + // not a collection - should not be checked + @Items(valid = {@Valid}) String strValue; + + String noAnnotations; } - // invalid - PlainGetter plainGetter = new PlainGetter(); - assertInvalid("testValidMethod", validator.validateTop(TOP, plainGetter), STR_FIELD, "null"); + Container cont = new Container(); + cont.strValue = STRING_VALUE; + cont.noAnnotations = STRING_VALUE; - // valid - plainGetter.strValue = STRING_VALUE; - assertTrue(validator.validateTop(TOP, plainGetter).isValid()); + // null collection - always valid + assertTrue(validator.validateTop(TOP, cont).isValid()); - /* - * Static getter - should throw an exception. - */ - StaticGetter staticGetter = new StaticGetter(); - assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, staticGetter)) - .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG); + // empty collection - always valid + cont.items = List.of(); + assertTrue(validator.validateTop(TOP, cont).isValid()); - /* - * Protected getter - should throw an exception. - */ - class ProtectedGetter { - @NotNull - @Getter(AccessLevel.PROTECTED) - String strValue; - } + cont.items = List.of(-10, -20); + assertThat(validator.validateTop(TOP, cont).getResult()).contains("\"0\"", "-10", "\"1\"", "-20", "minimum"); - ProtectedGetter protectedGetter = new ProtectedGetter(); - assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, protectedGetter)) - .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG); + cont.items = List.of(10, -30); + assertThat(validator.validateTop(TOP, cont).getResult()).contains("\"1\"", "-30", "minimum") + .doesNotContain("\"0\""); - /* - * getter is a "void" function - should throw an exception. - */ - class VoidGetter { - @NotNull + cont.items = List.of(10, 20); + assertTrue(validator.validateTop(TOP, cont).isValid()); + } + + @Test + public void testVerMap() { + @Getter + class Container { + @Entries(key = @Items(), value = @Items(min = {@Min(5)})) + Map<String, Integer> items; + + // not a map - should not be checked + @Entries(key = @Items(), value = @Items(min = {@Min(5)})) String strValue; - @SuppressWarnings("unused") - public void getStrValue() { - // do nothing - } + String noAnnotations; } - VoidGetter voidGetter = new VoidGetter(); - assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, voidGetter)) - .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG); + Container cont = new Container(); + cont.strValue = STRING_VALUE; + cont.noAnnotations = STRING_VALUE; - /* - * getter takes an argument - should throw an exception. - */ - class ArgGetter { - @NotNull - String strValue; + // null map - always valid + assertTrue(validator.validateTop(TOP, cont).isValid()); - @SuppressWarnings("unused") - public String getStrValue(String echo) { - return echo; - } - } + // empty map - always valid + cont.items = Map.of(); + assertTrue(validator.validateTop(TOP, cont).isValid()); - ArgGetter argGetter = new ArgGetter(); - assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, argGetter)) - .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG); - } + cont.items = Map.of("abc", -10, "def", -20); + assertThat(validator.validateTop(TOP, cont).getResult()).contains("abc", "-10", "def", "-20", "minimum"); + cont.items = Map.of("abc", 10, "def", -30); + assertThat(validator.validateTop(TOP, cont).getResult()).contains("def", "-30", "minimum") + .doesNotContain("abc"); - private void assertInvalid(String testName, BeanValidationResult result, String fieldName, String message) { - String text = result.getResult(); - assertNotNull(testName, text); - assertTrue(testName, text.contains(fieldName)); - assertTrue(testName, text.contains(message)); + cont.items = Map.of("abc", 10, "def", 20); + assertTrue(validator.validateTop(TOP, cont).isValid()); } - /** - * Annotated static field. - */ - private static class AnnotFieldStatic { - @NotNull - static String strValue; - } + private <T> void assertNumeric(String testName, T object, Consumer<Integer> setter, String fieldName, + String expectedText, int inside, int edge, int outside) { + setter.accept(inside); + assertTrue(validator.validateTop(TOP, object).isValid()); - /** - * Annotated class with a static field. - */ - @NotNull - private static class AnnotClassStatic { - @SuppressWarnings("unused") - static String strValue; + // on the edge + setter.accept(edge); + assertTrue(validator.validateTop(TOP, object).isValid()); + + // invalid + setter.accept(outside); + assertInvalid("testVerNotNull", validator.validateTop(TOP, object), fieldName, expectedText); } - /** - * Class with an annotated field, but a static "getter". - */ - private static class StaticGetter { - @NotNull - String strValue; - @SuppressWarnings("unused") - public static String getStrValue() { - return STRING_VALUE; - } + private void assertInvalid(String testName, BeanValidationResult result, String... text) { + assertThat(result.getResult()).describedAs(testName).contains(text); } } diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestEntryValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestEntryValidator.java new file mode 100644 index 00000000..1c93d6c8 --- /dev/null +++ b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestEntryValidator.java @@ -0,0 +1,108 @@ +/*- + * ============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 org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.annotations.Items; +import org.onap.policy.common.parameters.annotations.Min; +import org.onap.policy.common.parameters.annotations.NotBlank; + +public class TestEntryValidator extends ValidatorUtil { + + // annotations for keys and values + + @Items() + private int emptyAnnot; + + @Items(notBlank = {@NotBlank}) + private int keyAnnot; + + @Items(min = {@Min(5)}) + private int valueAnnot; + + + @Before + public void setUp() { + bean = new BeanValidator(); + } + + @Test + public void testIsEmpty() { + // no annotations for key or value + assertThat(new EntryValidator(bean, getAnnot("emptyAnnot"), getAnnot("emptyAnnot")).isEmpty()).isTrue(); + + // annotations for key, value, or both + assertThat(new EntryValidator(bean, getAnnot("keyAnnot"), getAnnot("emptyAnnot")).isEmpty()).isFalse(); + assertThat(new EntryValidator(bean, getAnnot("emptyAnnot"), getAnnot("valueAnnot")).isEmpty()).isFalse(); + assertThat(new EntryValidator(bean, getAnnot("keyAnnot"), getAnnot("valueAnnot")).isEmpty()).isFalse(); + } + + @Test + public void testValidateEntry() { + EntryValidator validator = new EntryValidator(bean, getAnnot("keyAnnot"), getAnnot("valueAnnot")); + + // valid key & value + BeanValidationResult result = new BeanValidationResult(MY_NAME, this); + validator.validateEntry(result, makeEntry(HELLO, 10)); + assertThat(result.getResult()).isNull(); + + // invalid key + result = new BeanValidationResult(MY_NAME, this); + validator.validateEntry(result, makeEntry("", 20)); + assertThat(result.getResult()).doesNotContain("\"value\"").contains("\"key\"", "blank"); + + // invalid value + result = new BeanValidationResult(MY_NAME, this); + validator.validateEntry(result, makeEntry(HELLO, -10)); + assertThat(result.getResult()).contains(HELLO, "\"value\"", "-10").doesNotContain("\"key\""); + + // both invalid + result = new BeanValidationResult(MY_NAME, this); + validator.validateEntry(result, makeEntry("", -100)); + assertThat(result.getResult()).contains("\"key\"", "blank", "\"value\"", "-100"); + } + + @Test + public void testGetName() { + EntryValidator validator = new EntryValidator(bean, getAnnot("emptyAnnot"), getAnnot("emptyAnnot")); + assertThat(validator.getName(makeEntry(null, 0))).isEmpty(); + assertThat(validator.getName(makeEntry("", 0))).isEmpty(); + assertThat(validator.getName(makeEntry(HELLO, 0))).isEqualTo(HELLO); + } + + /** + * Makes a Map entry with the given key and value. + * + * @param key desired key + * @param value desired value + * @return a new Map entry + */ + Map.Entry<String, Integer> makeEntry(String key, int value) { + HashMap<String, Integer> map = new HashMap<>(); + map.put(key, value); + return map.entrySet().iterator().next(); + } +} diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestFieldValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestFieldValidator.java new file mode 100644 index 00000000..e4432c8d --- /dev/null +++ b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestFieldValidator.java @@ -0,0 +1,274 @@ +/*- + * ============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 org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +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.NotNull; + +public class TestFieldValidator extends ValidatorUtil { + private static final String UNANNOTATED_FIELD = "unannotated"; + private static final String INT_FIELD = "intValue"; + private static final int VALID_INT = 10; + private static final int INVALID_INT = -10; + + @Getter + private int unannotated; + + @Min(0) + @Getter + private int intValue; + + @NotNull + @Getter + private boolean boolValue; + + @NotNull + @Getter + private String notNullValue; + + @Min(0) + @Getter + private static int staticField; + + /** + * Has no accessor. + */ + @Min(0) + private int noMethod; + + /** + * Accessor is {@link #getStaticMethod()}, which is static. + */ + @Min(0) + private int staticMethod; + + /** + * Accessor is {@link #getVoidMethod()}, which returns a void. + */ + @Min(0) + private int voidMethod; + + /** + * Accessor is {@link #getParameterizedMethod()}, which requires a parameter. + */ + @Min(0) + private int parameterizedMethod; + + /** + * Accessor is {@link #getExMethod()}, which throws an exception. + */ + @Min(0) + private int exMethod; + + + @Before + public void setUp() { + bean = new BeanValidator(); + } + + @Test + public void testGetAnnotation() { + // field-level annotation + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse(); + + // class-level annotation + assertThat(new FieldValidator(bean, ClassAnnot.class, getField(ClassAnnot.class, "text")).isEmpty()).isFalse(); + } + + @Test + public void testFieldValidator() throws NoSuchFieldException, SecurityException { + /* + * Note: nested classes contain fields like "$this", thus the check for "$" in the + * variable name is already covered by the other tests. + */ + + /* + * Class with no annotations. + */ + @NotNull + class NoAnnotations { + @SuppressWarnings("unused") + String strValue; + } + + Field field = NoAnnotations.class.getDeclaredField("this$0"); + + assertThat(new FieldValidator(bean, NoAnnotations.class, field).isEmpty()).isTrue(); + + // unannotated + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("unannotated")).isEmpty()).isTrue(); + + // these are invalid for various reasons + + Field staticField2 = getField("staticField"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticField2)) + .isInstanceOf(IllegalArgumentException.class); + + Field noMethodField = getField("noMethod"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, noMethodField)) + .isInstanceOf(IllegalArgumentException.class); + + // annotated + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse(); + } + + @Test + public void testFieldValidator_SetNullAllowed() { + // default - null is allowed + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isNullAllowed()).isTrue(); + + // field-level NotNull + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("notNullValue")).isNullAllowed()) + .isFalse(); + + // class-level NotNull + assertThat(new FieldValidator(bean, ClassAnnot.class, getField(ClassAnnot.class, "noMethod")).isNullAllowed()) + .isFalse(); + } + + @Test + public void testValidateField_testGetValue() { + // unannotated + BeanValidationResult result = new BeanValidationResult(MY_NAME, this); + new FieldValidator(bean, getClass(), getField(UNANNOTATED_FIELD)).validateField(result, this); + assertThat(result.getResult()).isNull(); + + // valid + intValue = VALID_INT; + result = new BeanValidationResult(MY_NAME, this); + new FieldValidator(bean, getClass(), getField(INT_FIELD)).validateField(result, this); + assertThat(result.getResult()).isNull(); + + // invalid + intValue = INVALID_INT; + result = new BeanValidationResult(MY_NAME, this); + new FieldValidator(bean, getClass(), getField(INT_FIELD)).validateField(result, this); + assertThat(result.getResult()).contains(INT_FIELD); + + // throws an exception + FieldValidator validator = new FieldValidator(bean, TestFieldValidator.class, getField("exMethod")); + BeanValidationResult result2 = new BeanValidationResult(MY_NAME, this); + assertThatThrownBy(() -> validator.validateField(result2, this)).isInstanceOf(IllegalArgumentException.class) + .getCause().isInstanceOf(InvocationTargetException.class).getCause() + .hasMessage("expected exception"); + } + + @Test + public void testClassOnly() { + // class-level annotation has no bearing on a static field + assertThat(new FieldValidator(bean, ClassAnnot.class, getField(ClassAnnot.class, "staticValue")).isEmpty()) + .isTrue(); + + // field-level annotation on a static field + Field staticField2 = getField("staticField"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticField2)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void testGetAccessor() { + // uses "getXxx" + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse(); + + // uses "isXxx" + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("boolValue")).isEmpty()).isFalse(); + } + + @Test + public void testGetMethod() { + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse(); + + // these are invalid for various reasons + + Field noMethodField = getField("noMethod"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, noMethodField)) + .isInstanceOf(IllegalArgumentException.class); + + Field staticMethodField = getField("staticMethod"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticMethodField)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void testValidMethod() { + assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse(); + + // these are invalid for various reasons + + Field staticMethodField = getField("staticMethod"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticMethodField)) + .isInstanceOf(IllegalArgumentException.class); + + Field voidMethodField = getField("voidMethod"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, voidMethodField)) + .isInstanceOf(IllegalArgumentException.class); + + Field paramMethodField = getField("parameterizedMethod"); + assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, paramMethodField)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void testIsFieldAnnotated_testSetFieldAnnotated() { + // annotated at the field level + assertThat(new FieldValidator(bean, getClass(), getField(INT_FIELD)).isFieldAnnotated()).isTrue(); + + // unannotated + assertThat(new FieldValidator(bean, getClass(), getField(UNANNOTATED_FIELD)).isFieldAnnotated()).isFalse(); + } + + public static int getStaticMethod() { + return -1000; + } + + public void getVoidMethod() { + // do nothing + } + + public int getParameterizedMethod(boolean flag) { + return 0; + } + + public int getExMethod() { + throw new RuntimeException("expected exception"); + } + + @NotNull + public static class ClassAnnot { + @Getter + private String text; + + // no "get" method + @SuppressWarnings("unused") + private String noMethod; + + @Getter + private static int staticValue; + } +} diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestItemValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestItemValidator.java new file mode 100644 index 00000000..2a0394dc --- /dev/null +++ b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestItemValidator.java @@ -0,0 +1,161 @@ +/*- + * ============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 static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.annotations.Items; +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 TestItemValidator 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. + */ + @SimpleItems(simple = {@Simple}) + private int mismatch; + + /** + * Annotation with no sub-annotations. + */ + @Items() + private int noAnnotations; + + /** + * One matching sub-annotation. + */ + @Items(notNull = {@NotNull}) + private int match; + + /** + * Excess matching sub-annotations of a single type. + */ + @Items(notNull = {@NotNull, @NotNull}) + private int excess; + + /** + * Multiple matching annotations. + */ + @Items(notNull = {@NotNull}, notBlank = {@NotBlank}) + private String multiMatch; + + + @Before + public void setUp() { + bean = new BeanValidator(); + } + + @Test + public void testGetAnnotation() { + // no matches + assertThat(new ItemValidator(bean, getAnnot("noAnnotations"), true).isEmpty()).isTrue(); + + // had a match + assertThat(new ItemValidator(bean, getAnnot("match"), true).isEmpty()).isFalse(); + + // with an exception + IllegalAccessException ex = new IllegalAccessException("expected exception"); + + assertThatThrownBy(() -> new ItemValidator(bean, getAnnot("match"), true) { + @Override + protected <T extends Annotation> T getAnnotation2(Class<T> annotClass, Method method) + throws IllegalAccessException { + throw ex; + } + }).hasCause(ex); + + // multiple matches + ItemValidator validator = new ItemValidator(bean, getAnnot("multiMatch"), true); + + 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 testItemValidatorBeanValidatorAnnotation() { + assertThat(new ItemValidator(bean, getAnnot("match")).isEmpty()).isFalse(); + } + + @Test + public void testItemValidatorBeanValidatorAnnotationBoolean() { + assertThat(new ItemValidator(bean, getAnnot("match"), true).isEmpty()).isFalse(); + + assertThat(new ItemValidator(bean, getAnnot("match"), false).isEmpty()).isTrue(); + } + + @Test + public void testGetAnnotation2() { + assertThat(new ItemValidator(bean, getAnnot("notArray"), true).isEmpty()).isTrue(); + assertThat(new ItemValidator(bean, getAnnot("mismatch"), true).isEmpty()).isTrue(); + assertThat(new ItemValidator(bean, getAnnot("noAnnotations"), true).isEmpty()).isTrue(); + + assertThat(new ItemValidator(bean, getAnnot("match"), true).isEmpty()).isFalse(); + + Annotation excess = getAnnot("excess"); + assertThatThrownBy(() -> new ItemValidator(bean, excess, true)).isInstanceOf(IllegalArgumentException.class); + } + + // these annotations are not recognized by the BeanValidator + + @Retention(RUNTIME) + @Target(FIELD) + public @interface Simple { + + } + + @Retention(RUNTIME) + @Target(FIELD) + public @interface SimpleItems { + /** + * Validates that it's simple. + */ + Simple[] simple() default {}; + } +} diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestValueValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestValueValidator.java new file mode 100644 index 00000000..dcf08695 --- /dev/null +++ b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestValueValidator.java @@ -0,0 +1,142 @@ +/*- + * ============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 org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.Annotation; +import java.util.concurrent.atomic.AtomicBoolean; +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 TestValueValidator extends ValidatorUtil { + + private ValueValidator validator; + + // these fields just provide place-holders for annotations + + @NotNull + @NotBlank + private final int annotField = 1; + + + @Before + public void setUp() { + validator = new MyValueValidator(); + } + + @Test + public void testIsEmpty() { + assertThat(validator.isEmpty()).isTrue(); + + validator.addAnnotation(NotNull.class, (result2, fieldName, value) -> true); + assertThat(validator.isEmpty()).isFalse(); + } + + @Test + public void testValidateValue_NullValue() { + BeanValidationResult result = new BeanValidationResult(MY_NAME, this); + + validator.validateValue(result, MY_FIELD, null); + assertThat(result.getResult()).isNull(); + + validator.addAnnotation(NotNull.class, + (result2, fieldName, value) -> result2.validateNotNull(fieldName, value)); + validator.validateValue(result, MY_FIELD, null); + assertThat(result.getResult()).contains(MY_FIELD, "null"); + } + + @Test + public void testValidateValue_NotNullValue() { + BeanValidationResult result = new BeanValidationResult(MY_NAME, this); + + validator.validateValue(result, MY_FIELD, HELLO); + assertThat(result.getResult()).isNull(); + + validator.addAnnotation(NotNull.class, + (result2, fieldName, value) -> result2.validateNotNull(fieldName, value)); + validator.validateValue(result, MY_FIELD, HELLO); + assertThat(result.getResult()).isNull(); + } + + @Test + public void testAddAnnotationClassOfTChecker() { + // the field does not have this annotation + validator.addAnnotation(Min.class, (result2, fieldName, value) -> true); + assertThat(validator.isEmpty()).isTrue(); + + // "null" flag should stay true with this annotation + assertThat(validator.isNullAllowed()).isTrue(); + validator.addAnnotation(NotBlank.class, (result2, fieldName, value) -> true); + assertThat(validator.isNullAllowed()).isTrue(); + + // "null" flag should become false with this annotation + assertThat(validator.isNullAllowed()).isTrue(); + validator.addAnnotation(NotNull.class, (result2, fieldName, value) -> true); + assertThat(validator.isNullAllowed()).isFalse(); + } + + @Test + public void testAddAnnotationClassOfTCheckerWithAnnotOfT() { + // the field does not have this annotation + validator.addAnnotation(Min.class, (result2, fieldName, annot, value) -> true); + assertThat(validator.isEmpty()).isTrue(); + + // indicates the annotation value + AtomicBoolean wasNull = new AtomicBoolean(false); + + // the field DOES have this annotation + validator.addAnnotation(NotNull.class, (result2, fieldName, annot, value) -> { + wasNull.set(annot instanceof NotNull); + return result2.validateNotNull(fieldName, value); + }); + assertThat(validator.isEmpty()).isFalse(); + + // ensure that the checker is invoked + BeanValidationResult result = new BeanValidationResult(MY_NAME, this); + validator.validateValue(result, MY_FIELD, HELLO); + assertThat(result.getResult()).isNull(); + + assertThat(wasNull.get()).isTrue(); + } + + @Test + public void testGetAnnotation() { + assertThat(new ValueValidator().getAnnotation(NotNull.class)).isNull(); + } + + /** + * Checks for annotations on the "annotField" field. + */ + private static class MyValueValidator extends ValueValidator { + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotClass) { + try { + return TestValueValidator.class.getDeclaredField("annotField").getAnnotation(annotClass); + } catch (NoSuchFieldException | SecurityException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/ValidatorUtil.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/ValidatorUtil.java new file mode 100644 index 00000000..4df014f7 --- /dev/null +++ b/common-parameters/src/test/java/org/onap/policy/common/parameters/ValidatorUtil.java @@ -0,0 +1,72 @@ +/*- + * ============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.Field; + +/** + * Utilities for validator tests. + */ +public class ValidatorUtil { + protected static final String MY_NAME = "My-Name"; + protected static final String MY_FIELD = "My-Field"; + protected static final String HELLO = "hello"; + + protected BeanValidator bean; + + /** + * Gets the single annotation for a given field. + * + * @param fieldName name of the field having the desired annotation + * @return the given field's annotation + */ + protected Annotation getAnnot(String fieldName) { + return getField(fieldName).getAnnotations()[0]; + } + + /** + * Gets a field from this object. + * + * @param fieldName name of the field of interest + * @return the given field + */ + protected Field getField(String fieldName) { + return getField(getClass(), fieldName); + } + + /** + * Gets a field from a given class. + * + * @param clazz class containing the field + * @param fieldName name of the field of interest + * @return the given field + */ + protected Field getField(Class<?> clazz, String fieldName) { + try { + return clazz.getDeclaredField(fieldName); + + } catch (NoSuchFieldException | SecurityException e) { + throw new RuntimeException(e); + } + } + +} |