aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Hahn <jrh3@att.com>2020-12-18 15:53:20 -0500
committerJim Hahn <jrh3@att.com>2020-12-18 21:17:12 -0500
commit5f1b1162d047d2a743f1ce57cc17494a6150c75c (patch)
tree2098ecd2bc94ec6d0d2325568651d2418b659084
parente2756fa0a8054854dfaaac1c7fa064d207a0a4ae (diff)
Make validators extensible
Modified the validator to make it extensible. Also added annotations to: - cascade a validation to a sub-object - perform regex - examine items in a list - examine entries in a map Still need more junit tests. Issue-ID: POLICY-2648 Change-Id: I94f1b9e8fbf7a6b9b002d0b05cc9119bdfcf8bf2 Signed-off-by: Jim Hahn <jrh3@att.com>
-rw-r--r--common-parameters/pom.xml4
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java423
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/EntryValidator.java84
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java216
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/ItemValidator.java108
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/ValueValidator.java158
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Entries.java45
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Items.java66
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Pattern.java37
-rw-r--r--common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Valid.java34
10 files changed, 996 insertions, 179 deletions
diff --git a/common-parameters/pom.xml b/common-parameters/pom.xml
index c7136bd3..c0efcf70 100644
--- a/common-parameters/pom.xml
+++ b/common-parameters/pom.xml
@@ -31,6 +31,10 @@
<description>[${project.parent.artifactId}] module provides common property and parameter handling the ONAP Policy Framework</description>
<dependencies>
+ <dependency>
+ <groupId>com.google.re2j</groupId>
+ <artifactId>re2j</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
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 dbd3c7ce..3f5abccc 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
@@ -22,33 +22,25 @@ package org.onap.policy.common.parameters;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
+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;
/**
* Bean validator, supporting the parameter annotations.
- * <p/>
- * Note: this currently does not support Min/Max validation of "short" or "byte"; these
- * annotations are simply ignored for these types.
*/
public class BeanValidator {
/**
- * {@code True} if there is a field-level annotation, {@code false} otherwise.
- */
- private boolean fieldIsAnnotated;
-
- /**
* Validates top level fields within an object. For each annotated field, it retrieves
* the value using the public "getter" method for the field. If there is no public
* "getter" method, then it throws an exception. Otherwise, it validates the retrieved
@@ -75,162 +67,95 @@ public class BeanValidator {
}
/**
- * Performs validation of all annotated fields found within the class.
+ * Adds validators based on the annotations that are available.
*
- * @param result validation results are added here
- * @param object object whose fields are to be validated
- * @param clazz class, within the object's hierarchy, to be examined for fields to be
- * verified
+ * @param validator where to add the validators
*/
- private void validateFields(BeanValidationResult result, Object object, Class<?> clazz) {
- for (Field field : clazz.getDeclaredFields()) {
- validateField(result, object, clazz, field);
- }
+ protected void addValidators(ValueValidator validator) {
+ validator.addAnnotation(NotNull.class, this::verNotNull);
+ validator.addAnnotation(NotBlank.class, this::verNotBlank);
+ validator.addAnnotation(Max.class, this::verMax);
+ validator.addAnnotation(Min.class, this::verMin);
+ validator.addAnnotation(Pattern.class, this::verRegex);
+ validator.addAnnotation(Valid.class, this::verCascade);
+ validator.addAnnotation(Items.class, this::verCollection);
+ validator.addAnnotation(Entries.class, this::verMap);
}
/**
- * Performs validation of a single field.
+ * Performs validation of all annotated fields found within the class.
*
* @param result validation results are added here
* @param object object whose fields are to be validated
- * @param clazz class, within the object's hierarchy, containing the field
- * @param field field whose value is to be validated
- */
- private void validateField(BeanValidationResult result, Object object, Class<?> clazz, Field field) {
- final String fieldName = field.getName();
- if (fieldName.contains("$")) {
- return;
- }
-
- /*
- * Identify the annotations. NotNull MUST be first so the check is run before the
- * others.
- */
- fieldIsAnnotated = false;
- List<Predicate<Object>> checkers = new ArrayList<>(10);
- addAnnotation(clazz, field, checkers, NotNull.class, (annot, value) -> verNotNull(result, fieldName, value));
- addAnnotation(clazz, field, checkers, NotBlank.class, (annot, value) -> verNotBlank(result, fieldName, value));
- addAnnotation(clazz, field, checkers, Max.class, (annot, value) -> verMax(result, fieldName, annot, value));
- addAnnotation(clazz, field, checkers, Min.class, (annot, value) -> verMin(result, fieldName, annot, value));
-
- if (checkers.isEmpty()) {
- // has no annotations - nothing to check
- return;
- }
-
- // verify the field type is of interest
- int mod = field.getModifiers();
- if (Modifier.isStatic(mod)) {
- classOnly(clazz.getName() + "." + fieldName + " is annotated but the field is static");
- return;
- }
-
- // get the field's "getter" method
- Method accessor = getAccessor(object.getClass(), fieldName);
- if (accessor == null) {
- classOnly(clazz.getName() + "." + fieldName + " is annotated but has no \"get\" method");
- return;
- }
-
- // get the value
- Object value = getValue(object, clazz, fieldName, accessor);
-
- // perform the checks
- if (value == null && field.getAnnotation(NotNull.class) == null && clazz.getAnnotation(NotNull.class) == null) {
- // value is null and there's no null check - just return
- return;
- }
-
- for (Predicate<Object> checker : checkers) {
- if (!checker.test(value)) {
- // invalid - don't bother with additional checks
- return;
- }
- }
- }
-
- /**
- * Gets the value from the object using the accessor function.
- *
- * @param object object whose value is to be retrieved
- * @param clazz class containing the field
- * @param fieldName name of the field
- * @param accessor "getter" method
- * @return the object's value
- */
- private Object getValue(Object object, Class<?> clazz, final String fieldName, Method accessor) {
- try {
- return accessor.invoke(object);
-
- } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
- throw new IllegalArgumentException(clazz.getName() + "." + fieldName + " accessor threw an exception", e);
- }
- }
-
- /**
- * Throws an exception if there are field-level annotations.
- *
- * @param exceptionMessage exception message
+ * @param clazz class, within the object's hierarchy, to be examined for fields to be
+ * verified
*/
- private void classOnly(String exceptionMessage) {
- if (fieldIsAnnotated) {
- throw new IllegalArgumentException(exceptionMessage);
+ private void validateFields(BeanValidationResult result, Object object, Class<?> clazz) {
+ for (Field field : clazz.getDeclaredFields()) {
+ FieldValidator validator = makeFieldValidator(clazz, field);
+ validator.validateField(result, object);
}
}
/**
- * Looks for an annotation at the class or field level. If an annotation is found at
- * either the field or class level, then it adds a verifier to the list of checkers.
+ * Verifies that the value is not null.
*
- * @param clazz class to be searched
- * @param field field to be searched
- * @param checkers where to place the new field verifier
- * @param annotClass class of annotation to find
- * @param check verification function to be added to the list, if the annotation is
- * found
+ * @param result where to add the validation result
+ * @param fieldName field whose value is being verified
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
*/
- private <T extends Annotation> void addAnnotation(Class<?> clazz, Field field, List<Predicate<Object>> checkers,
- Class<T> annotClass, BiPredicate<T, Object> check) {
-
- // field annotation takes precedence over class annotation
- T annot = field.getAnnotation(annotClass);
- if (annot != null) {
- fieldIsAnnotated = true;
-
- } else if ((annot = clazz.getAnnotation(annotClass)) == null) {
- return;
+ public boolean verNotNull(BeanValidationResult result, String fieldName, Object value) {
+ if (value == null) {
+ ObjectValidationResult result2 =
+ new ObjectValidationResult(fieldName, xlate(value), ValidationStatus.INVALID, "is null");
+ result.addResult(result2);
+ return false;
}
- T annot2 = annot;
- checkers.add(value -> check.test(annot2, value));
+ return true;
}
/**
- * Verifies that the value is not null.
+ * Verifies that the value is not blank. Note: this does <i>not</i> verify that the
+ * value is not {@code null}.
*
* @param result where to add the validation result
* @param fieldName field whose value is being verified
- * @param value value to be verified, assumed to be non-null
- * @return {@code true} if the value is valid, {@code false} otherwise
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
*/
- private boolean verNotNull(BeanValidationResult result, String fieldName, Object value) {
- return result.validateNotNull(fieldName, value);
+ public boolean verNotBlank(BeanValidationResult result, String fieldName, Object value) {
+ if (value instanceof String && StringUtils.isBlank(value.toString())) {
+ ObjectValidationResult result2 =
+ new ObjectValidationResult(fieldName, xlate(value), ValidationStatus.INVALID, "is blank");
+ result.addResult(result2);
+ return false;
+ }
+
+ return true;
}
/**
- * Verifies that the value is not blank.
+ * Verifies that the value matches a regular expression.
*
* @param result where to add the validation result
* @param fieldName field whose value is being verified
- * @param value value to be verified, assumed to be non-null
- * @return {@code true} if the value is valid, {@code false} otherwise
+ * @param annot annotation against which the value is being verified
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
*/
- private boolean verNotBlank(BeanValidationResult result, String fieldName, Object value) {
- if (value instanceof String && StringUtils.isBlank(value.toString())) {
- ObjectValidationResult fieldResult =
- new ObjectValidationResult(fieldName, value, ValidationStatus.INVALID, "is blank");
- result.addResult(fieldResult);
- return false;
+ 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;
+ }
+ } catch (RuntimeException e) {
+ // TODO log at trace level
+ return true;
}
return true;
@@ -242,10 +167,10 @@ public class BeanValidator {
* @param result where to add the validation result
* @param fieldName field whose value is being verified
* @param annot annotation against which the value is being verified
- * @param value value to be verified, assumed to be non-null
- * @return {@code true} if the value is valid, {@code false} otherwise
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
*/
- private boolean verMax(BeanValidationResult result, String fieldName, Max annot, Object value) {
+ public boolean verMax(BeanValidationResult result, String fieldName, Max annot, Object value) {
if (!(value instanceof Number)) {
return true;
}
@@ -265,9 +190,9 @@ public class BeanValidator {
return true;
}
- ObjectValidationResult fieldResult = new ObjectValidationResult(fieldName, value, ValidationStatus.INVALID,
+ ObjectValidationResult result2 = new ObjectValidationResult(fieldName, xlate(value), ValidationStatus.INVALID,
"exceeds the maximum value: " + annot.value());
- result.addResult(fieldResult);
+ result.addResult(result2);
return false;
}
@@ -277,22 +202,35 @@ public class BeanValidator {
* @param result where to add the validation result
* @param fieldName field whose value is being verified
* @param annot annotation against which the value is being verified
- * @param value value to be verified, assumed to be non-null
- * @return {@code true} if the value is valid, {@code false} otherwise
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
+ */
+ public boolean verMin(BeanValidationResult result, String fieldName, Min annot, Object value) {
+ return verMin(result, fieldName, annot.value(), value);
+ }
+
+ /**
+ * Verifies that the value is >= the minimum value.
+ *
+ * @param result where to add the validation result
+ * @param fieldName field whose value is being verified
+ * @param min minimum against which the value is being verified
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
*/
- private boolean verMin(BeanValidationResult result, String fieldName, Min annot, Object value) {
+ public boolean verMin(BeanValidationResult result, String fieldName, long min, Object value) {
if (!(value instanceof Number)) {
return true;
}
Number num = (Number) value;
if (num instanceof Integer || num instanceof Long) {
- if (num.longValue() >= annot.value()) {
+ if (num.longValue() >= min) {
return true;
}
} else if (num instanceof Float || num instanceof Double) {
- if (num.doubleValue() >= annot.value()) {
+ if (num.doubleValue() >= min) {
return true;
}
@@ -300,54 +238,181 @@ public class BeanValidator {
return true;
}
- ObjectValidationResult fieldResult = new ObjectValidationResult(fieldName, value, ValidationStatus.INVALID,
- "is below the minimum value: " + annot.value());
- result.addResult(fieldResult);
+ ObjectValidationResult result2 = new ObjectValidationResult(fieldName, xlate(value), ValidationStatus.INVALID,
+ "is below the minimum value: " + min);
+ result.addResult(result2);
return false;
}
/**
- * Gets an accessor method for the given field.
+ * Verifies that the value is valid by recursively invoking
+ * {@link #validateTop(String, Object)}.
*
- * @param clazz class whose methods are to be searched
- * @param fieldName field whose "getter" is to be identified
- * @return the field's "getter" method, or {@code null} if it is not found
+ * @param result where to add the validation result
+ * @param fieldName field whose value is being verified
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
*/
- private Method getAccessor(Class<?> clazz, String fieldName) {
- String capname = StringUtils.capitalize(fieldName);
- Method accessor = getMethod(clazz, "get" + capname);
- if (accessor != null) {
- return accessor;
+ public boolean verCascade(BeanValidationResult result, String fieldName, Object value) {
+ if (value == null || value instanceof Collection || value instanceof Map) {
+ return true;
}
- return getMethod(clazz, "is" + capname);
+ BeanValidationResult result2 = validateTop(fieldName, value);
+
+ if (result2.isClean()) {
+ return true;
+ }
+
+ result.addResult(result2);
+
+ return result2.isValid();
}
/**
- * Gets the "getter" method having the specified name.
+ * Validates the items in a collection.
*
- * @param clazz class whose methods are to be searched
- * @param methodName name of the method of interest
- * @return the method, or {@code null} if it is not found
+ * @param result where to add the validation result
+ * @param fieldName name of the field containing the collection
+ * @param annot validation annotations for individual items
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
*/
- private Method getMethod(Class<?> clazz, String methodName) {
- for (Method method : clazz.getMethods()) {
- if (methodName.equals(method.getName()) && validMethod(method)) {
- return method;
- }
+ public boolean verCollection(BeanValidationResult result, String fieldName, Annotation annot, Object value) {
+
+ if (!(value instanceof Collection)) {
+ return true;
+ }
+
+ ItemValidator itemValidator = makeItemValidator(annot);
+
+ return verCollection(result, fieldName, itemValidator, value);
+ }
+
+ /**
+ * Validates the items in a collection.
+ *
+ * @param result where to add the validation result
+ * @param fieldName name of the field containing the collection
+ * @param itemValidator validator for individual items within the list
+ * @param value value to be verified
+ * @return {@code true} if the next check should be performed, {@code false} otherwise
+ */
+ public boolean verCollection(BeanValidationResult result, String fieldName, ValueValidator itemValidator,
+ Object value) {
+
+ if (!(value instanceof Collection) || itemValidator.isEmpty()) {
+ return true;
+ }
+
+ Collection<?> list = (Collection<?>) value;
+
+ BeanValidationResult result2 = new BeanValidationResult(fieldName, value);
+ int count = 0;
+ for (Object item : list) {
+ itemValidator.validateValue(result2, String.valueOf(count++), item);
+ }
+
+ if (result2.isClean()) {
+ return true;
+ }
+
+ result.addResult(result2);
+ 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 annot validation annotations for individual entries
+ * @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, Entries annot, Object value) {
+
+ if (!(value instanceof Map)) {
+ return true;
+ }
+
+ EntryValidator entryValidator = makeEntryValidator(annot.key(), annot.value());
+
+ return verMap(result, fieldName, entryValidator, value);
+ }
+
+ /**
+ * Validates the items in a Map.
+ *
+ * @param result where to add the validation result
+ * @param fieldName name of the field containing the map
+ * @param entryValidator validator for individual entries within the Map
+ * @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, EntryValidator entryValidator, Object value) {
+
+ if (!(value instanceof Map)) {
+ return true;
+ }
+
+ Map<?, ?> map = (Map<?, ?>) value;
+
+ BeanValidationResult result2 = new BeanValidationResult(fieldName, value);
+
+ for (Entry<?, ?> entry : map.entrySet()) {
+ entryValidator.validateEntry(result2, entry);
+ }
+
+ if (result2.isClean()) {
+ return true;
}
- return null;
+ result.addResult(result2);
+ return false;
+ }
+
+ /**
+ * Makes a field validator.
+ *
+ * @param clazz class containing the field
+ * @param field field of interest
+ * @return a validator for the given field
+ */
+ protected FieldValidator makeFieldValidator(Class<?> clazz, Field field) {
+ return new FieldValidator(this, clazz, field);
+ }
+
+ /**
+ * Makes an item validator.
+ *
+ * @param annot container for the item annotations
+ * @return a new item validator
+ */
+ protected ItemValidator makeItemValidator(Annotation annot) {
+ return new ItemValidator(this, annot);
+ }
+
+ /**
+ * Makes an entry validator.
+ *
+ * @param keyAnnot container for the annotations associated with the entry key
+ * @param valueAnnot container for the annotations associated with the entry value
+ * @return a new entry validator
+ */
+ protected EntryValidator makeEntryValidator(Annotation keyAnnot, Annotation valueAnnot) {
+ return new EntryValidator(this, keyAnnot, valueAnnot);
}
/**
- * Determines if a method is a valid "getter".
+ * Translates a value to something printable, for use by
+ * {@link ObjectValidationResult}. This default method simply returns the original
+ * value.
*
- * @param method method to be checked
- * @return {@code true} if the method is a valid "getter", {@code false} otherwise
+ * @param value value to be translated
+ * @return the translated value
*/
- private boolean validMethod(Method method) {
- int mod = method.getModifiers();
- return !(Modifier.isStatic(mod) || method.getReturnType() == void.class || method.getParameterCount() != 0);
+ public Object xlate(Object value) {
+ return value;
}
}
diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/EntryValidator.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/EntryValidator.java
new file mode 100644
index 00000000..965c95e5
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/EntryValidator.java
@@ -0,0 +1,84 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.util.Map;
+
+/**
+ * Validator of an entry within a Map.
+ */
+public class EntryValidator {
+ private final ItemValidator keyValidator;
+ private final ItemValidator valueValidator;
+
+ /**
+ * Constructs the object.
+ *
+ * @param validator provider of validation methods
+ * @param keyAnnotationContainer an annotation containing validation annotations to be
+ * applied to the entry key
+ * @param valueAnnotationContainer an annotation containing validation annotations to
+ * be applied to the entry value
+ */
+ public EntryValidator(BeanValidator validator, Annotation keyAnnotationContainer,
+ Annotation valueAnnotationContainer) {
+ keyValidator = new ItemValidator(validator, keyAnnotationContainer);
+ valueValidator = new ItemValidator(validator, valueAnnotationContainer);
+ }
+
+ public boolean isEmpty() {
+ return (keyValidator.isEmpty() && valueValidator.isEmpty());
+ }
+
+ /**
+ * Performs validation of a single entry.
+ *
+ * @param result validation results are added here
+ * @param entry value to be validated
+ */
+ public <K, V> void validateEntry(BeanValidationResult result, Map.Entry<K, V> entry) {
+ String name = getName(entry);
+
+ BeanValidationResult result2 = new BeanValidationResult(name, entry);
+ keyValidator.validateValue(result2, "key", entry.getKey());
+ valueValidator.validateValue(result2, "value", entry.getValue());
+
+ if (!result2.isClean()) {
+ result.addResult(result2);
+ }
+ }
+
+ /**
+ * Gets a name for the entry.
+ *
+ * @param entry entry whose name is to be determined
+ * @return a name for the entry
+ */
+ protected <K, V> String getName(Map.Entry<K, V> entry) {
+ K key = entry.getKey();
+ if (key == null) {
+ return "";
+ }
+
+ return key.toString();
+ }
+}
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
new file mode 100644
index 00000000..e762dc0e
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java
@@ -0,0 +1,216 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+/**
+ * Validator of the contents of a field, supporting the parameter annotations.
+ */
+public class FieldValidator extends ValueValidator {
+
+ /**
+ * {@code True} if there is a field-level annotation, {@code false} otherwise.
+ */
+ @Getter
+ @Setter(AccessLevel.PROTECTED)
+ private boolean fieldAnnotated = false;
+
+ /**
+ * Class containing the field of interest.
+ */
+ private final Class<?> clazz;
+
+ /**
+ * Field of interest.
+ */
+ private final Field field;
+
+ /**
+ * Method to retrieve the field's value.
+ */
+ private Method accessor;
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param validator provider of validation methods
+ * @param clazz class containing the field
+ * @param field field whose value is to be validated
+ */
+ public FieldValidator(BeanValidator validator, Class<?> clazz, Field field) {
+ this.clazz = clazz;
+ this.field = field;
+
+ String fieldName = field.getName();
+ if (fieldName.contains("$")) {
+ return;
+ }
+
+ validator.addValidators(this);
+
+ if (checkers.isEmpty()) {
+ // has no annotations - nothing to check
+ return;
+ }
+
+ // verify the field type is of interest
+ int mod = field.getModifiers();
+ if (Modifier.isStatic(mod)) {
+ classOnly(clazz.getName() + "." + fieldName + " is annotated but the field is static");
+ checkers.clear();
+ return;
+ }
+
+ // get the field's "getter" method
+ accessor = getAccessor(clazz, fieldName);
+ if (accessor == null) {
+ classOnly(clazz.getName() + "." + fieldName + " is annotated but has no \"get\" method");
+ checkers.clear();
+ return;
+ }
+
+ // determine if null is allowed
+ if (field.getAnnotation(NotNull.class) != null || clazz.getAnnotation(NotNull.class) != null) {
+ setNullAllowed(false);
+ }
+ }
+
+ /**
+ * Performs validation of a single field.
+ *
+ * @param result validation results are added here
+ * @param object object whose field is to be validated
+ */
+ public void validateField(BeanValidationResult result, Object object) {
+ if (isEmpty()) {
+ // has no annotations - nothing to check
+ return;
+ }
+
+ // get the value
+ Object value = getValue(object, accessor);
+
+ validateValue(result, field.getName(), value);
+ }
+
+ /**
+ * Gets the value from the object using the accessor function.
+ *
+ * @param object object whose value is to be retrieved
+ * @param accessor "getter" method
+ * @return the object's value
+ */
+ private Object getValue(Object object, Method accessor) {
+ try {
+ return accessor.invoke(object);
+
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new IllegalArgumentException(clazz.getName() + "." + field.getName() + " accessor threw an exception",
+ e);
+ }
+ }
+
+ /**
+ * Throws an exception if there are field-level annotations.
+ *
+ * @param exceptionMessage exception message
+ */
+ private void classOnly(String exceptionMessage) {
+ if (isFieldAnnotated()) {
+ throw new IllegalArgumentException(exceptionMessage);
+ }
+ }
+
+ /**
+ * Gets an annotation from the field or the class.
+ *
+ * @param annotClass annotation class of interest
+ * @return the annotation, or {@code null} if neither the field nor the class has the
+ * desired annotation
+ */
+ public <T extends Annotation> T getAnnotation(Class<T> annotClass) {
+
+ // field annotation takes precedence over class annotation
+ T annot = field.getAnnotation(annotClass);
+ if (annot != null) {
+ setFieldAnnotated(true);
+ return annot;
+ }
+
+ return clazz.getAnnotation(annotClass);
+ }
+
+ /**
+ * Gets an accessor method for the given field.
+ *
+ * @param clazz class whose methods are to be searched
+ * @param fieldName field whose "getter" is to be identified
+ * @return the field's "getter" method, or {@code null} if it is not found
+ */
+ private Method getAccessor(Class<?> clazz, String fieldName) {
+ String capname = StringUtils.capitalize(fieldName);
+ Method accessor = getMethod(clazz, "get" + capname);
+ if (accessor != null) {
+ return accessor;
+ }
+
+ return getMethod(clazz, "is" + capname);
+ }
+
+ /**
+ * Gets the "getter" method having the specified name.
+ *
+ * @param clazz class whose methods are to be searched
+ * @param methodName name of the method of interest
+ * @return the method, or {@code null} if it is not found
+ */
+ private Method getMethod(Class<?> clazz, String methodName) {
+ for (Method method : clazz.getMethods()) {
+ if (methodName.equals(method.getName()) && validMethod(method)) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines if a method is a valid "getter".
+ *
+ * @param method method to be checked
+ * @return {@code true} if the method is a valid "getter", {@code false} otherwise
+ */
+ private boolean validMethod(Method method) {
+ int mod = method.getModifiers();
+ return !(Modifier.isStatic(mod) || method.getReturnType() == void.class || method.getParameterCount() != 0);
+ }
+}
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
new file mode 100644
index 00000000..d0c027c1
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/ItemValidator.java
@@ -0,0 +1,108 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * 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 ItemValidator extends ValueValidator {
+ private final Annotation annotationContainer;
+
+ /**
+ * Constructs the object.
+ *
+ * @param validator provider of validation methods
+ * @param annotationContainer an annotation containing validation annotations to be
+ * applied to the item
+ */
+ public ItemValidator(BeanValidator validator, Annotation annotationContainer) {
+ this(validator, annotationContainer, true);
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @param validator provider of validation methods
+ * @param annotationContainer an annotation containing validation annotations to be
+ * applied to the item
+ * @param addValidators {@code true} if to add validators
+ */
+ public ItemValidator(BeanValidator validator, Annotation annotationContainer, boolean addValidators) {
+ this.annotationContainer = annotationContainer;
+
+ 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 #annotationContainer} does
+ * not contain the desired annotation
+ */
+ public <T extends Annotation> T getAnnotation(Class<T> annotClass) {
+ try {
+ for (Method meth : annotationContainer.getClass().getDeclaredMethods()) {
+ T annot = getAnnotation2(annotClass, meth);
+ if (annot != null) {
+ return annot;
+ }
+ }
+ } catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalArgumentException("cannot determine " + annotClass.getName(), e);
+ }
+
+ return null;
+ }
+
+ private <T extends Annotation> T getAnnotation2(Class<T> annotClass, Method method)
+ throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+ Class<?> ret = method.getReturnType();
+ if (!ret.isArray()) {
+ return null;
+ }
+
+ Class<?> comp = ret.getComponentType();
+ if (comp != annotClass) {
+ return null;
+ }
+
+ // get the array for this type of annotation
+ @SuppressWarnings("unchecked")
+ T[] arrobj = (T[]) method.invoke(annotationContainer);
+
+ if (arrobj.length == 0) {
+ return null;
+ }
+
+ // TODO log if there's more than one item
+
+ 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
new file mode 100644
index 00000000..9095bfd0
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/ValueValidator.java
@@ -0,0 +1,158 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.util.ArrayList;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+/**
+ * Validator of a value.
+ * <p/>
+ * Note: this currently does not support Min/Max validation of "short" or "byte"; these
+ * annotations are simply ignored for these types.
+ */
+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.
+ */
+ @Getter
+ @Setter(AccessLevel.PROTECTED)
+ private boolean nullAllowed = true;
+
+ /**
+ * Predicates to invoke to validate an object.
+ * <p/>
+ * Note: each predicate is expected to return {@code true} if the next check is
+ * allowed to proceed, {@code false} otherwise. In addition, if {@link #nullAllowed}
+ * is {@code true}, then the predicates must be prepared to deal with a {@code null}
+ * Object as their input parameter.
+ */
+ protected List<Checker> checkers = new ArrayList<>(10);
+
+
+ /**
+ * Constructs the object.
+ */
+ public ValueValidator() {
+ // do nothing
+ }
+
+ /**
+ * Determines if the validator has anything to check.
+ *
+ * @return {@code true} if the validator is empty (i.e., has nothing to check)
+ */
+ public boolean isEmpty() {
+ return checkers.isEmpty();
+ }
+
+ /**
+ * Performs validation of a single field.
+ *
+ * @param result validation results are added here
+ * @param fieldName field whose value is being verified
+ * @param value value to be validated
+ */
+ protected void validateValue(BeanValidationResult result, String fieldName, Object value) {
+
+ if (value == null && isNullAllowed()) {
+ // value is null and null is allowed - just return
+ return;
+ }
+
+ for (Checker checker : checkers) {
+ if (!checker.test(result, fieldName, value)) {
+ // invalid - don't bother with additional checks
+ return;
+ }
+ }
+ }
+
+ /**
+ * Looks for an annotation at the class or field level. If an annotation is found at
+ * either the field or class level, then it adds a verifier to
+ * {@link ValueValidator#checkers}.
+ *
+ * @param annotClass class of annotation to find
+ * @param checker function to validate the value
+ */
+ public <T extends Annotation> void addAnnotation(Class<T> annotClass, Checker checker) {
+ T annot = getAnnotation(annotClass);
+ if (annot != null) {
+ checkers.add(checker);
+
+ if (annotClass == NotNull.class) {
+ setNullAllowed(false);
+ }
+ }
+ }
+
+ /**
+ * Looks for an annotation at the class or field level. If an annotation is found at
+ * either the field or class level, then it adds a verifier to
+ * {@link ValueValidator#checkers}.
+ *
+ * @param annotClass class of annotation to find
+ * @param checker function to validate the value
+ */
+ public <T extends Annotation> void addAnnotation(Class<T> annotClass, CheckerWithAnnot<T> checker) {
+ T annot = getAnnotation(annotClass);
+ if (annot != null) {
+ checkers.add((result, fieldName, value) -> checker.test(result, fieldName, annot, value));
+ }
+ }
+
+ /**
+ * Gets an annotation from the field or the class. The default method simply returns
+ * {@code null}.
+ *
+ * @param annotClass annotation class of interest
+ * @return the annotation, or {@code null} if neither the field nor the class has the
+ * desired annotation
+ */
+ public <T extends Annotation> T getAnnotation(Class<T> annotClass) {
+ return null;
+ }
+
+ // functions to validate a value extracted from a field
+
+ public static interface Checker {
+ boolean test(BeanValidationResult result, String fieldName, Object value);
+ }
+
+ public static interface CheckerWithAnnot<T extends Annotation> {
+ boolean test(BeanValidationResult result, String fieldName, T annotation, Object value);
+ }
+}
diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Entries.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Entries.java
new file mode 100644
index 00000000..89c9ce26
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Entries.java
@@ -0,0 +1,45 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Validations on entries within a Map.
+ */
+@Retention(RUNTIME)
+@Target(FIELD)
+public @interface Entries {
+
+ /**
+ * Validations to perform on each entry's key.
+ */
+ Items key();
+
+ /**
+ * Validations to perform on each entry's value.
+ */
+ Items value();
+}
diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Items.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Items.java
new file mode 100644
index 00000000..d022d95e
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Items.java
@@ -0,0 +1,66 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Validations on individual items, typically within a collection.
+ */
+@Retention(RUNTIME)
+@Target(FIELD)
+public @interface Items {
+
+ /**
+ * Validates the item is not {@code null}.
+ */
+ NotNull[] notNull() default {};
+
+ /**
+ * Validates the item is not blank.
+ */
+ NotBlank[] notBlank() default {};
+
+ /**
+ * Validates the item matches a regular expression.
+ */
+ Pattern[] pattern() default {};
+
+ /**
+ * Validates the item is not greater than a certain value.
+ */
+ Max[] max() default {};
+
+ /**
+ * Validates the item is not less than a certain value.
+ */
+ Min[] min() default {};
+
+ /**
+ * Validates the item is valid, using a {@link BeanValidator}.
+ */
+ Valid[] valid() default {};
+
+}
diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Pattern.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Pattern.java
new file mode 100644
index 00000000..a30d814c
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Pattern.java
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target(FIELD)
+public @interface Pattern {
+
+ /**
+ * Regular expression to be matched.
+ */
+ String regexp();
+}
diff --git a/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Valid.java b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Valid.java
new file mode 100644
index 00000000..a3a1d59f
--- /dev/null
+++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/annotations/Valid.java
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 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.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({TYPE, FIELD})
+public @interface Valid {
+
+}