/*-
* ============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 java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
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;
/**
* Bean validator, supporting the parameter annotations.
*
* 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
* 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) {
BeanValidationResult 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;
}
/**
* 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()) {
validateField(result, object, clazz, field);
}
}
/**
* Performs validation of a single field.
*
* @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> 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