diff options
Diffstat (limited to 'common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java')
-rw-r--r-- | common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java | 432 |
1 files changed, 432 insertions, 0 deletions
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 new file mode 100644 index 00000000..4ed7e42c --- /dev/null +++ b/common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java @@ -0,0 +1,432 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * 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. + * 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.reflect.Field; +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.ClassName; +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.Size; +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 + * 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 + * value based on the annotations. This recurses through super classes looking for + * fields to be verified, but it does not examine any interfaces. + * + * @param name name of the object being validated + * @param object object to be validated. If {@code null}, then an empty result is + * returned + * @return the validation result + */ + public BeanValidationResult validateTop(String name, Object object) { + var result = new BeanValidationResult(name, object); + if (object == null) { + return result; + } + + // check class hierarchy - don't need to check interfaces + for (Class<?> clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass()) { + validateFields(result, object, clazz); + } + + return result; + } + + /** + * Adds validators based on the annotations that are available. + * + * @param validator where to add the validators + */ + protected void addValidators(ValueValidator validator) { + validator.addAnnotation(NotNull.class, this::verNotNull); + validator.addAnnotation(NotBlank.class, this::verNotBlank); + validator.addAnnotation(Size.class, this::verSize); + validator.addAnnotation(Max.class, this::verMax); + validator.addAnnotation(Min.class, this::verMin); + validator.addAnnotation(Pattern.class, this::verRegex); + validator.addAnnotation(ClassName.class, this::verClassName); + validator.addAnnotation(Valid.class, this::verCascade); + } + + /** + * 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, to be examined for fields to be + * verified + */ + private void validateFields(BeanValidationResult result, Object object, Class<?> clazz) { + for (Field field : clazz.getDeclaredFields()) { + var validator = makeFieldValidator(clazz, field); + validator.validateField(result, object); + } + } + + /** + * Verifies that the value is not null. + * + * @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 + */ + public boolean verNotNull(BeanValidationResult result, String fieldName, Object value) { + if (value == null) { + result.addResult(fieldName, xlate(value), ValidationStatus.INVALID, "is null"); + return false; + } + + return true; + } + + /** + * 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 + * @return {@code true} if the next check should be performed, {@code false} otherwise + */ + public boolean verNotBlank(BeanValidationResult result, String fieldName, Object value) { + if (value instanceof String && StringUtils.isBlank(value.toString())) { + result.addResult(fieldName, xlate(value), ValidationStatus.INVALID, "is blank"); + return false; + } + + return true; + } + + /** + * Verifies that the value has the specified number of elements. + * + * @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 + * @return {@code true} if the next check should be performed, {@code false} otherwise + */ + public boolean verSize(BeanValidationResult result, String fieldName, Size annot, Object value) { + int size; + if (value instanceof Collection) { + size = ((Collection<?>) value).size(); + + } else if (value instanceof Map) { + size = ((Map<?, ?>) value).size(); + + } else { + return true; + } + + + if (size < annot.min()) { + result.addResult(fieldName, xlate(value), ValidationStatus.INVALID, + "minimum number of elements: " + annot.min()); + return false; + } + + return true; + } + + /** + * 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 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 + */ + 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())) { + return true; + } + + } catch (RuntimeException e) { + logger.warn("validation error for regular expression: {}", annot.regexp(), e); + } + + result.addResult(fieldName, xlate(value), ValidationStatus.INVALID, + "does not match regular expression " + annot.regexp()); + return false; + } + + /** + * 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 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 + */ + public boolean verMax(BeanValidationResult result, String fieldName, Max annot, Object value) { + if (!(value instanceof Number)) { + return true; + } + + Number num = (Number) value; + if (num instanceof Integer || num instanceof Long) { + if (num.longValue() <= annot.value()) { + return true; + } + + } else if (num instanceof Float || num instanceof Double) { + if (num.doubleValue() <= annot.value()) { + return true; + } + + } else { + return true; + } + + result.addResult(fieldName, xlate(value), ValidationStatus.INVALID, + "exceeds the maximum value: " + annot.value()); + return false; + } + + /** + * 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 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 + */ + 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 + */ + 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() >= min) { + return true; + } + + } else if (num instanceof Float || num instanceof Double) { + if (num.doubleValue() >= min) { + return true; + } + + } else { + return true; + } + + result.addResult(fieldName, xlate(value), ValidationStatus.INVALID, + "is below the minimum value: " + min); + return false; + } + + /** + * Verifies that the value is a valid class name. + * + * @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 + */ + public boolean verClassName(BeanValidationResult result, String fieldName, Object value) { + if (!(value instanceof String)) { + return true; + } + + try { + Class.forName(value.toString()); + return true; + + } catch (final ClassNotFoundException exp) { + result.addResult(fieldName, value, ValidationStatus.INVALID, "class is not in the classpath"); + return false; + } + } + + /** + * Verifies that the value is valid by recursively invoking + * {@link #validateTop(String, Object)}. + * + * @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 + */ + public boolean verCascade(BeanValidationResult result, String fieldName, Object value) { + if (value == null || value instanceof Collection || value instanceof Map) { + return true; + } + + BeanValidationResult result2 = (value instanceof ParameterGroup ? ((ParameterGroup) value).validate() + : validateTop(fieldName, value)); + + if (result2.isClean()) { + return true; + } + + result.addResult(result2); + + return result2.isValid(); + } + + /** + * 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)) { + return true; + } + + Collection<?> list = (Collection<?>) value; + + var result2 = new BeanValidationResult(fieldName, value); + var 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 keyValidator validator for an individual key within the Map entry + * @param valueValidator validator for an individual value within the Map entry + * @param value value to be verified + * @return {@code true} if the next check should be performed, {@code false} otherwise + */ + public boolean verMap(BeanValidationResult result, String fieldName, ValueValidator keyValidator, + ValueValidator valueValidator, Object value) { + + if (!(value instanceof Map)) { + return true; + } + + Map<?, ?> map = (Map<?, ?>) value; + + var result2 = new BeanValidationResult(fieldName, value); + + for (Entry<?, ?> entry : map.entrySet()) { + String name = getEntryName(entry); + + var result3 = new BeanValidationResult(name, entry); + keyValidator.validateValue(result3, "key", entry.getKey()); + valueValidator.validateValue(result3, "value", entry.getValue()); + + if (!result3.isClean()) { + result2.addResult(result3); + } + } + + if (result2.isClean()) { + return true; + } + + result.addResult(result2); + return false; + } + + /** + * Gets a name for an entry. + * + * @param entry entry whose name is to be determined + * @return a name for the entry + */ + protected <K, V> String getEntryName(Map.Entry<K, V> entry) { + var key = entry.getKey(); + if (key == null) { + return ""; + } + + return key.toString(); + } + + /** + * 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); + } + + /** + * Translates a value to something printable, for use by + * {@link ObjectValidationResult}. This default method simply returns the original + * value. + * + * @param value value to be translated + * @return the translated value + */ + public Object xlate(Object value) { + return value; + } +} |