All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.yaas.servicesdk.auditbase.validation.DefaultValidator Maven / Gradle / Ivy

The newest version!
/*
 * © 2017 SAP SE or an SAP affiliate company.
 * All rights reserved.
 * Please see http://www.sap.com/corporate-en/legal/copyright/index.epx for additional trademark information and
 * notices.
 */
package com.sap.cloud.yaas.servicesdk.auditbase.validation;

import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;


/**
 * Default validator that evaluates the annotations on the fields and types ({@link Validated}, {@link Mandatory} and
 * {@link CustomValidated}). It checks:
 *
 * - those fields
 * - nested Collections
 *
 * recursively. Checking of nested Objects is not implemented.
 *
 * @param  the type of object that will be validated
 */
public class DefaultValidator implements Validator
{

	// If true, will treat every class as if it was annotated with @Validated. Needed only for test, because of
	// https://bugs.openjdk.java.net/browse/JDK-8059531.
	private boolean ignoreValidated;

	/**
	 * Validates the object according to the {@link Validated}, {@link Mandatory} and {@link CustomValidated}
	 * annotations.
	 * Only classes marked with {@link Validated} will be validated.
	 *
	 * @param object the object to validate
	 * @return the collection of possible validation violations or empty collection
	 */
	public Collection validate(final Object object)
	{
		return validateInternal(object, object.getClass(), null);
	}

	/**
	 * Validates the object in the context of the given class (the fields from superclass are not visible in subclass),
	 * passing the information about the propertyPath to keep track of the property path within the object.
	 *
	 * @param object the object to validate
	 * @param clazzContext the class or superclass of the object which to investigate
	 * @param propertyPath the String representation of the property path
	 * @return the collection of possible validation violations or empty collection
	 */
	protected Collection validateInternal(
			final Object object,
			final Class clazzContext,
			final String propertyPath)
	{
		final Collection result = new ArrayList<>();

		checkSuperClass(result, clazzContext, object);

		if (ignoreValidated || clazzContext.isAnnotationPresent(Validated.class))
		{
			for (final Field field : clazzContext.getDeclaredFields())
			{
				final boolean accessible = field.isAccessible();
				field.setAccessible(true);
				try
				{
					checkMandatoryFields(result, field, object, propertyPath);

					checkRegexValidatedFields(result, field, object, propertyPath);

					checkCustomValidations(result, field, object);

					checkNestedCollections(result, field, object, propertyPath);
				}
				catch (final InstantiationException | IllegalAccessException e)
				{
					throw new IllegalStateException("Unexpected error: " + e.getMessage(), e);
				}
				finally
				{
					field.setAccessible(accessible);
				}
			}
		}

		return result;
	}

	/**
	 * Resolves the path of current field. Either it had some parents and they should be prepended with a ".", or maybe
	 * it is even a part of a collection, then the index should be printed before.
	 *
	 * @param fieldName the field name
	 * @param propertyPath the field property path up to now, or null
	 * @param indexInOwner if a member of collection, the index in that collection; if not then null
	 * @return the field's path
	 */
	private String resolvePath(final String fieldName, final String propertyPath, final Integer indexInOwner)
	{
		if (indexInOwner == null && propertyPath != null)
		{
			return String.format("%s.%s", propertyPath, fieldName);
		}
		else if (indexInOwner != null)
		{
			return String.format("%s[%s]", fieldName, indexInOwner);
		}
		else
		{
			return fieldName;
		}
	}

	/**
	 * Validated fields of the superclass, unless it is the Object class.
	 *
	 * @param validationViolations the collection to fill in
	 * @param clazzContext the class or superclass of the object
	 * @param object the object to validate
	 */
	private void checkSuperClass(
			final Collection validationViolations,
			final Class clazzContext,
			final Object object)
	{
		if (!Object.class.equals(clazzContext))
		{
			validationViolations.addAll(validateInternal(object, clazzContext.getSuperclass(), null));
		}
	}

	/**
	 * Checks a field if it is a collection (aka ParameterizedType) and if yes, validates all its members.
	 *
	 * @param validationViolations the collection to fill in
	 * @param field the field to check if it contains the collection and if yes validate its members
	 * @param object the object who owns the field
	 * @param propertyPath the field property path up to now, or null
	 * @throws IllegalAccessException if a field is not accessible by reflection
	 */
	private void checkNestedCollections(
			final Collection validationViolations,
			final Field field,
			final Object object,
			final String propertyPath)
			throws IllegalAccessException
	{
		if (field.getGenericType() instanceof ParameterizedType)
		{
			final ParameterizedType collectionType = (ParameterizedType) field.getGenericType();
			final Class clazz = (Class) collectionType.getActualTypeArguments()[0];

			if (field.get(object) instanceof Collection)
			{
				final Collection collection = (Collection) field.get(object);
				int idx = 0;
				for (final Object ob : collection)
				{
					validationViolations.addAll(validateInternal(ob, clazz, resolvePath(field.getName(), propertyPath, idx)));
					idx++;
				}
			}
		}
	}

	/**
	 * Checks for {@link CustomValidated} annotation on the field and evaluates it.
	 *
	 * @param validationViolations the collection to fill in
	 * @param field the field to check
	 * @param object the object that contains the field
	 * @throws InstantiationException if the custom validator cannot be instantiated
	 * @throws IllegalAccessException if a field is not accessible by reflection
	 */
	private void checkCustomValidations(
			final Collection validationViolations,
			final Field field,
			final Object object)
			throws InstantiationException, IllegalAccessException
	{
		if (field.isAnnotationPresent(CustomValidated.class))
		{
			final CustomValidated annotation = field.getAnnotation(CustomValidated.class);
			final Class>> validatorClass = annotation
					.validator();
			final Function> validator = validatorClass.newInstance();
			validationViolations.addAll(validator.apply(field.get(object)));

		}
	}

	/**
	 * Checks for {@link Mandatory} annotation on the field and evaluates it.
	 *
	 * @param validationViolations the collection to fill in
	 * @param field the field to check
	 * @param object the object which owns the field
	 * @param propertyPath the field property path up to now, or null
	 * @throws IllegalAccessException if a field is not accessible by reflection
	 */
	private void checkMandatoryFields(
			final Collection validationViolations,
			final Field field,
			final Object object,
			final String propertyPath)
			throws IllegalAccessException
	{
		if (field.isAnnotationPresent(Mandatory.class))
		{
			final String fieldName = resolveFieldName(field);
			final Object value = field.get(object);
			if (value == null
					|| value instanceof String && StringUtils.isEmpty(String.valueOf(value))
					|| value instanceof Collection && ((Collection) value).isEmpty())
			{
				validationViolations.add(new ValidationViolation(resolvePath(fieldName, propertyPath, null), "Is a required field"));
			}
		}
	}

	/**
	 * Checks for {@link RegexValidated} annotation on the field and evaluates it.
	 *
	 * @param validationViolations the collection to fill in
	 * @param field the field to check
	 * @param object the object which owns the field
	 * @param propertyPath the field property path up to now, or null
	 * @throws IllegalAccessException if a field is not accessible by reflection
	 */
	private void checkRegexValidatedFields(
			final Collection validationViolations,
			final Field field,
			final Object object,
			final String propertyPath)
			throws IllegalAccessException
	{
		if (field.isAnnotationPresent(RegexValidated.class))
		{
			final Object value = field.get(object);
			if (value instanceof String)
			{
				final RegexValidated annotation = field.getAnnotation(RegexValidated.class);
				final String regex = annotation.value();
				final String fieldName = resolveFieldName(field);

				final String str = (String) value;
				if (!str.matches(regex))
				{
					validationViolations.add(new ValidationViolation(
							resolvePath(fieldName, propertyPath, null), "Should match " + regex));
				}
			}
		}
	}

	private String resolveFieldName(final Field field)
	{
		final Mandatory mandatoryAnnotation = field.getAnnotation(Mandatory.class);
		return mandatoryAnnotation != null && StringUtils.isNotEmpty(mandatoryAnnotation.name())
				? mandatoryAnnotation.name() : field.getName();
	}

	void setIgnoreValidated(final boolean ignoreValidated)
	{
		this.ignoreValidated = ignoreValidated;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy