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

io.micronaut.validation.validator.DefaultValidator Maven / Gradle / Ivy

/*
 * Copyright 2017-2020 original authors
 *
 * 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
 *
 * https://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.
 */
package io.micronaut.validation.validator;

import io.micronaut.aop.Intercepted;
import io.micronaut.context.BeanResolutionContext;
import io.micronaut.context.ExecutionHandleLocator;
import io.micronaut.context.annotation.ConfigurationReader;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Property;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.exceptions.BeanInstantiationException;
import io.micronaut.core.annotation.AnnotatedElement;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.beans.BeanIntrospector;
import io.micronaut.core.beans.BeanProperty;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ArgumentValue;
import io.micronaut.core.type.MutableArgumentValue;
import io.micronaut.core.type.ReturnType;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.InjectionPoint;
import io.micronaut.inject.ProxyBeanDefinition;
import io.micronaut.inject.annotation.AnnotatedElementValidator;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.annotation.MutableAnnotationMetadata;
import io.micronaut.inject.validation.BeanDefinitionValidator;
import io.micronaut.validation.annotation.ValidatedElement;
import io.micronaut.validation.validator.constraints.ConstraintValidator;
import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
import io.micronaut.validation.validator.constraints.ConstraintValidatorRegistry;
import io.micronaut.validation.validator.extractors.ValueExtractorDefinition;
import io.micronaut.validation.validator.extractors.ValueExtractorRegistry;
import jakarta.inject.Singleton;
import jakarta.validation.ClockProvider;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintDeclarationException;
import jakarta.validation.ConstraintTarget;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.MessageInterpolator;
import jakarta.validation.Payload;
import jakarta.validation.TraversableResolver;
import jakarta.validation.UnexpectedTypeException;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import jakarta.validation.constraintvalidation.SupportedValidationTarget;
import jakarta.validation.constraintvalidation.ValidationTarget;
import jakarta.validation.metadata.BeanDescriptor;
import jakarta.validation.metadata.ConstraintDescriptor;
import jakarta.validation.metadata.ValidateUnwrappedValue;
import jakarta.validation.valueextraction.ValueExtractor;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Default implementation of the {@link Validator} interface.
 *
 * @author graemerocher
 * @author Andriy Dmytruk
 * @since 1.2
 */
@Singleton
@Primary
@Requires(property = ValidatorConfiguration.ENABLED, value = StringUtils.TRUE, defaultValue = StringUtils.TRUE)
public class DefaultValidator implements
    Validator, ExecutableMethodValidator, ReactiveValidator, AnnotatedElementValidator, BeanDefinitionValidator {

    private static final ValueExtractor LEGACY_ARRAY_EXTRACTOR = (originalValue, receiver) -> {
        int i = 0;
        for (Object item : originalValue) {
            receiver.indexedValue("", i++, item);
        }
    };

    final MessageInterpolator messageInterpolator;

    private final ConstraintValidatorRegistry constraintValidatorRegistry;
    private final ClockProvider clockProvider;
    private final ValueExtractorRegistry valueExtractorRegistry;
    private final TraversableResolver traversableResolver;
    private final ExecutionHandleLocator executionHandleLocator;
    private final ConversionService conversionService;
    private final BeanIntrospector beanIntrospector;

    /**
     * Default constructor.
     *
     * @param configuration The validator configuration
     */
    public DefaultValidator(@NonNull ValidatorConfiguration configuration) {
        requireNonNull("configuration", configuration);
        this.constraintValidatorRegistry = configuration.getConstraintValidatorRegistry();
        this.clockProvider = configuration.getClockProvider();
        this.valueExtractorRegistry = configuration.getValueExtractorRegistry();
        this.traversableResolver = configuration.getTraversableResolver();
        this.executionHandleLocator = configuration.getExecutionHandleLocator();
        this.messageInterpolator = configuration.getMessageInterpolator();
        this.conversionService = configuration.getConversionService();
        this.beanIntrospector = configuration.getBeanIntrospector();
    }

    /**
     * @return The clock provider
     */
    ClockProvider getClockProvider() {
        return clockProvider;
    }

    /**
     * @return The bean introspector
     */
    public BeanIntrospector getBeanIntrospector() {
        return beanIntrospector;
    }

    @NonNull
    @Override
    public  Set> validate(@NonNull T object, @Nullable Class... groups) {
        requireNonNull("object", object);
        final BeanIntrospection introspection = getBeanIntrospection(object);
        if (introspection == null) {
            throw new ValidationException("Bean introspection not found for the class: " + object.getClass());
        }
        return validate(introspection, object, groups);
    }

    /**
     * Validate the given introspection and object.
     *
     * @param introspection The introspection
     * @param object        The object
     * @param groups        The groups
     * @param            The object type
     * @return The constraint violations
     */
    @Override
    @SuppressWarnings("ConstantConditions")
    @NonNull
    public  Set> validate(@NonNull BeanIntrospection introspection,
                                                    @NonNull T object,
                                                    @NonNull Class... groups) {
        if (introspection == null) {
            throw new ValidationException("Passed object [" + object + "] cannot be introspected. Please annotate with @Introspected");
        }
        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, introspection, object, groups);
        doValidate(context, introspection, object);
        return context.getOverallViolations();
    }

    @NonNull
    @Override
    public  Set> validateProperty(@NonNull T object,
                                                            @NonNull String propertyName,
                                                            @NonNull Class... groups) {
        requireNonNull("object", object);
        requireNonEmpty("propertyName", propertyName);
        requireNonNull("groups", groups);
        final BeanIntrospection introspection = getBeanIntrospection(object);
        if (introspection == null) {
            throw new ValidationException("Passed object [" + object + "] cannot be introspected. Please annotate with @Introspected");
        }

        final Optional> property = introspection.getProperty(propertyName);
        if (property.isEmpty()) {
            throw new IllegalArgumentException("Cannot find property with name: " + property);
        }

        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, introspection, object, groups);

        for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences(introspection)) {
            try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {
                visitProperty(context, object, property.get(), false);
                if (validation.isFailed()) {
                    return Collections.unmodifiableSet(context.getOverallViolations());
                }
            }
        }

        return Collections.unmodifiableSet(context.getOverallViolations());
    }

    @NonNull
    @Override
    public  Set> validateValue(@NonNull Class beanType,
                                                         @NonNull String propertyName,
                                                         @Nullable Object value,
                                                         @NonNull Class... groups) {
        requireNonNull("beanType", beanType);
        requireNonEmpty("propertyName", propertyName);
        requireNonNull("groups", groups);

        final BeanIntrospection introspection = getBeanIntrospection(beanType);
        if (introspection == null) {
            throw new ValidationException("Passed bean type [" + beanType + "] cannot be introspected. Please annotate with @Introspected");
        }

        final BeanProperty beanProperty = introspection.getProperty(propertyName)
            .orElseThrow(() -> new IllegalArgumentException("No property [" + propertyName + "] found on type: " + beanType));

        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, introspection, null, groups);

        try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addPropertyNode(beanProperty.getName())) {
            if (isNotReachable(context, null)) {
                return Collections.unmodifiableSet(context.getOverallViolations());
            }
            for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences(introspection)) {
                try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {

                    visitElement(context, null, beanProperty.asArgument(), beanProperty.asArgument().getAnnotationMetadata(), value, false);

                    if (validation.isFailed()) {
                        return Collections.unmodifiableSet(context.getOverallViolations());
                    }
                }
            }
        }

        return Collections.unmodifiableSet(context.getOverallViolations());
    }

    @NonNull
    @Override
    public Set validatedAnnotatedElement(@NonNull AnnotatedElement element, @Nullable Object value) {
        requireNonNull("element", element);
        if (!element.getAnnotationMetadata().hasStereotype(Constraint.class)) {
            return Collections.emptySet();
        }

        final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, value);

        Argument type = value != null ? Argument.of((Class) value.getClass(), element.getAnnotationMetadata()) : Argument.OBJECT_ARGUMENT;

        boolean canCascade = true;
        try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addPropertyNode(element.getName())) {
            for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences()) {
                try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {

                    visitElement(context, element, type, value, canCascade);

                    if (validation.isFailed()) {
                        return context.getOverallViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toUnmodifiableSet());
                    }
                }
                canCascade = false;
            }
        }

        return context.getOverallViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toUnmodifiableSet());
    }

    @NonNull
    @Override
    public  T createValid(@NonNull Class beanType, Object... arguments) throws ConstraintViolationException {
        requireNonNull("type", beanType);

        final BeanIntrospection introspection = getBeanIntrospection(beanType);
        if (introspection == null) {
            throw new ValidationException("Passed bean type [" + beanType + "] cannot be introspected. Please annotate with @Introspected");
        }

        final Set> constraintViolations = validateConstructorParameters(introspection, arguments);
        if (!constraintViolations.isEmpty()) {
            throw new ConstraintViolationException(constraintViolations);
        }

        final T instance = introspection.instantiate(arguments);
        final Set> errors = validate(introspection, instance);
        if (errors.isEmpty()) {
            return instance;
        }
        throw new ConstraintViolationException(errors);
    }

    @Override
    public BeanDescriptor getConstraintsForClass(Class clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException();
        }
        return beanIntrospector.findIntrospection(clazz)
            .map((Function, BeanDescriptor>) IntrospectedBeanDescriptor::new)
            .orElseGet(() -> new EmptyDescriptor(clazz));
    }

    @Override
    public  T unwrap(Class type) {
        throw new UnsupportedOperationException("Validator unwrapping not supported by this implementation");
    }

    @Override
    @NonNull
    public ExecutableMethodValidator forExecutables() {
        return this;
    }

    @NonNull
    @Override
    public  Set> validateParameters(@NonNull T object,
                                                              @NonNull ExecutableMethod method,
                                                              @NonNull Object[] parameterValues,
                                                              @NonNull Class... groups) {
        requireNonNull("parameterValues", parameterValues);
        requireNonNull("object", object);
        requireNonNull("method", method);
        requireNonNull("groups", groups);
        final Argument[] arguments = method.getArguments();
        final int argLen = arguments.length;
        if (argLen != parameterValues.length) {
            throw new IllegalArgumentException("The method parameter array must have exactly " + argLen + " elements.");
        }

        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, object, groups);
        try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameterValues)) {
            try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addMethodNode(method)) {
                AnnotationMetadata methodAnnotationMetadata = method.getAnnotationMetadata().getDeclaredMetadata();
                validateParametersInternal(context, object, methodAnnotationMetadata, parameterValues, arguments, argLen);
            }
        }
        return Collections.unmodifiableSet(context.getOverallViolations());
    }

    @NonNull
    @Override
    public  Set> validateParameters(@NonNull T object,
                                                              @NonNull ExecutableMethod method,
                                                              @NonNull Collection> argumentValues,
                                                              @NonNull Class... groups) {
        requireNonNull("object", object);
        requireNonNull("method", method);
        requireNonNull("parameterValues", argumentValues);
        requireNonNull("groups", groups);

        final Argument[] arguments = method.getArguments();
        final int argLen = arguments.length;
        if (argLen != argumentValues.size()) {
            throw new IllegalArgumentException("The method parameter array must have exactly " + argLen + " elements.");
        }

        Object[] parameters = argumentValues.stream().map(ArgumentValue::getValue).toArray();

        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, object, groups);
        try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameters)) {
            try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addMethodNode(method)) {
                AnnotationMetadata methodAnnotationMetadata = method.getAnnotationMetadata().getDeclaredMetadata();
                validateParametersInternal(context, object, methodAnnotationMetadata, parameters, arguments, argLen);
            }
        }
        return Collections.unmodifiableSet(context.getOverallViolations());
    }

    @NonNull
    @Override
    public  Set> validateParameters(@NonNull T object,
                                                              @NonNull Method method,
                                                              @NonNull Object[] parameterValues,
                                                              @NonNull Class... groups) {
        requireNonNull("method", method);
        requireNonNull("groups", groups);

        return executionHandleLocator.findExecutableMethod(
                method.getDeclaringClass(),
                method.getName(),
                method.getParameterTypes()
            ).map(executableMethod -> validateParameters(object, executableMethod, parameterValues, groups))
            .orElse(Collections.emptySet());
    }

    @NonNull
    @Override
    public  Set> validateReturnValue(@NonNull T object,
                                                               @NonNull Method method,
                                                               @Nullable Object returnValue,
                                                               @NonNull Class... groups) {
        requireNonNull("method", method);
        requireNonNull("object", object);
        requireNonNull("groups", groups);

        return executionHandleLocator.findExecutableMethod(
                method.getDeclaringClass(),
                method.getName(),
                method.getParameterTypes()
            ).map(executableMethod -> validateReturnValue(object, executableMethod, returnValue, groups))
            .orElse(Collections.emptySet());
    }

    @Override
    public @NonNull  Set> validateReturnValue(@NonNull T bean,
                                                                        @NonNull ExecutableMethod executableMethod,
                                                                        @Nullable Object returnValue,
                                                                        @NonNull Class... groups) {
        requireNonNull("groups", groups);

        final ReturnType returnType = executableMethod.getReturnType();
        final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, bean, groups);

        try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableReturnValue(returnValue)) {
            try (ValidationPath.ContextualPath ignored2 = context.getCurrentPath().addMethodNode(executableMethod)) {
                try (ValidationPath.ContextualPath ignored3 = context.getCurrentPath().addReturnValueNode()) {
                    List groupSequences;
                    if (bean == null) {
                        groupSequences = context.findGroupSequences();
                    } else {
                        BeanIntrospection beanIntrospection = getBeanIntrospection(bean);
                        if (beanIntrospection == null) {
                            groupSequences = context.findGroupSequences();
                        } else {
                            groupSequences = context.findGroupSequences(beanIntrospection);
                        }
                    }

                    boolean canCascade = true;
                    for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : groupSequences) {
                        try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {
                            // Strip class annotations
                            AnnotationMetadata returnAm = returnType.asArgument().getAnnotationMetadata();
                            if (returnAm instanceof AnnotationMetadataHierarchy annotationMetadataHierarchy) {
                                if (returnAm.getDeclaredMetadata() instanceof AnnotationMetadataHierarchy) {
                                    returnAm = new AnnotationMetadataHierarchy(
                                        annotationMetadataHierarchy.getRootMetadata(),
                                        annotationMetadataHierarchy.getDeclaredMetadata().getDeclaredMetadata()
                                    );
                                } else {
                                    returnAm = annotationMetadataHierarchy.getDeclaredMetadata();
                                }
                            }
                            visitElement(context, bean, returnType.asArgument(), returnAm, returnValue, canCascade, false);

                            if (validation.isFailed()) {
                                return context.getOverallViolations();
                            }
                        }
                        canCascade = false;
                    }
                }
            }
        }

        return context.getOverallViolations();
    }

    @NonNull
    @Override
    public  Set> validateConstructorParameters(@NonNull Constructor constructor,
                                                                         @NonNull Object[] parameterValues,
                                                                         @NonNull Class... groups) {
        requireNonNull("constructor", constructor);
        requireNonNull("groups", groups);

        final Class declaringClass = constructor.getDeclaringClass();
        final BeanIntrospection introspection = getBeanIntrospection(declaringClass);
        return validateConstructorParameters(introspection, parameterValues);
    }

    @Override
    @NonNull
    public  Set> validateConstructorParameters(@NonNull BeanIntrospection introspection,
                                                                         @NonNull Object[] parameterValues,
                                                                         @NonNull Class... groups) {
        requireNonNull("introspection", introspection);
        requireNonNull("groups", groups);

        final Class beanType = introspection.getBeanType();
        final Argument[] constructorArguments = introspection.getConstructorArguments();
        return validateConstructorParameters(beanType, constructorArguments, parameterValues, groups);
    }

    @Override
    public  Set> validateConstructorParameters(Class beanType,
                                                                         Argument[] constructorArguments,
                                                                         @NonNull Object[] parameterValues,
                                                                         @NonNull Class[] groups) {
        requireNonNull("groups", groups);

        //noinspection ConstantConditions
        parameterValues = parameterValues != null ? parameterValues : ArrayUtils.EMPTY_OBJECT_ARRAY;
        final int argLength = constructorArguments.length;
        if (parameterValues.length != argLength) {
            throw new IllegalArgumentException("Expected exactly [" + argLength + "] constructor arguments");
        }
        DefaultConstraintValidatorContext context = (DefaultConstraintValidatorContext) new DefaultConstraintValidatorContext<>(this, null, beanType, groups);
        try (DefaultConstraintValidatorContext.ValidationCloseable ignored1 = context.withExecutableParameterValues(parameterValues)) {
            try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addConstructorNode(beanType.getSimpleName(), constructorArguments)) {
                validateParametersInternal(context, null, AnnotationMetadata.EMPTY_METADATA, parameterValues, constructorArguments, argLength);
            }
        }
        return Collections.unmodifiableSet(context.getOverallViolations());
    }

    @NonNull
    @Override
    public  Set> validateConstructorReturnValue(@NonNull Constructor constructor,
                                                                          @NonNull T createdObject,
                                                                          @NonNull Class... groups) {
        requireNonNull("groups", groups);

        return validate(createdObject, groups);
    }

    @NonNull
    @Override
    public  Publisher validatePublisher(@NonNull ReturnType returnType,
                                              @NonNull Publisher publisher,
                                              @NonNull Class... groups) {
        requireNonNull("publisher", publisher);
        requireNonNull("returnType", returnType);
        requireNonNull("groups", groups);

        if (returnType.getTypeParameters().length == 0) {
            return publisher;
        }
        Argument typeParameter = returnType.getTypeParameters()[0];
        Argument> publisherArgument = Argument.of((Class>) publisher.getClass());

        Publisher output;
        if (Publishers.isSingle(returnType.getType())) {
            output = Mono.from(publisher).flatMap(value -> {
                Set> violations = validatePublisherValue(publisherArgument, publisher, groups, typeParameter, value, true);
                return violations.isEmpty() ? Mono.just(value) :
                    Mono.error(new ConstraintViolationException(violations));
            });
        } else {
            output = Flux.from(publisher).flatMap(value -> {
                Set> violations = validatePublisherValue(publisherArgument, publisher, groups, typeParameter, value, true);
                return violations.isEmpty() ? Flux.just(value) :
                    Flux.error(new ConstraintViolationException(violations));
            });
        }
        Class returnClass = returnType.getType();
        if (!Publisher.class.isAssignableFrom(returnClass)) {
            return (Publisher) output;
        }
        return Publishers.convertPublisher(conversionService, output, (Class) returnClass);
    }

    /**
     * A method used inside the {@link #validatePublisher} method.
     */
    private  Set> validatePublisherValue(Argument publisherArgument,
                                                                                @NonNull T publisher,
                                                                                Class[] groups,
                                                                                Argument valueArgument,
                                                                                E value,
                                                                                boolean canCascade
    ) {
        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, publisher, groups);
        try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addReturnValueNode()) {
            try (ValidationPath.ContextualPath ignored1 = context.getCurrentPath().addContainerElementNode("",
                ValidationPath.DefaultContainerContext.ofIterableContainer(publisherArgument.getType()))) {
                for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences()) {
                    try (DefaultConstraintValidatorContext.GroupsValidation ignore = context.withGroupSequence(groupSequence)) {
                        visitElement(context, publisher, valueArgument, value, canCascade);
                    }
                }
            }
        }
        return context.getOverallViolations();
    }

    @NonNull
    @Override
    public  CompletionStage validateCompletionStage(@NonNull CompletionStage completionStage,
                                                          @NonNull Argument argument,
                                                          @NonNull Class... groups) {
        requireNonNull("completionStage", completionStage);
        requireNonNull("groups", groups);

        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, groups);
        for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences()) {
            try (DefaultConstraintValidatorContext.GroupsValidation ignore = context.withGroupSequence(groupSequence)) {
                return instrumentCompletionStage(context, completionStage, argument, true);
            }
        }
        return completionStage;
    }

    @Override
    public  void validateBeanArgument(@NonNull BeanResolutionContext resolutionContext,
                                         @NonNull InjectionPoint injectionPoint,
                                         @NonNull Argument argument,
                                         int index,
                                         @Nullable T value) throws BeanInstantiationException {
        final AnnotationMetadata annotationMetadata = argument.getAnnotationMetadata();
        final boolean hasValid = annotationMetadata.hasStereotype(Valid.class);
        final boolean hasConstraint = annotationMetadata.hasStereotype(Constraint.class);

        if (!hasConstraint && !hasValid) {
            return;
        }

        DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, value);

        final Class rootClass = injectionPoint.getDeclaringBean().getBeanType();

        boolean canCascade = true;
        try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addConstructorNode(
            rootClass.getName(), injectionPoint.getDeclaringBean().getConstructor().getArguments())) {
            try (ValidationPath.ContextualPath ignored1 = context.getCurrentPath().addPropertyNode(argument.getName())) {
                try (DefaultConstraintValidatorContext.ValidationCloseable ignore4 = context.convertGroups(argument.getAnnotationMetadata())) {
                    for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences()) {
                        try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {

                            visitElement(context, null, argument, value, canCascade);

                            if (validation.isFailed()) {
                                failOnError(resolutionContext, context.getOverallViolations(), rootClass);
                            }
                        }
                        canCascade = false;
                    }
                }
            }
        }

        failOnError(resolutionContext, context.getOverallViolations(), rootClass);
    }

    @Override
    public  void validateBean(@NonNull BeanResolutionContext resolutionContext,
                                 @NonNull BeanDefinition definition,
                                 @NonNull T bean) throws BeanInstantiationException {
        Class beanType;
        if (definition instanceof ProxyBeanDefinition proxyBeanDefinition) {
            beanType = (Class) proxyBeanDefinition.getTargetType();
        } else {
            beanType = definition.getBeanType();
        }
        final BeanIntrospection introspection = getBeanIntrospection(bean, beanType);
        if (introspection != null) {
            Set> errors = validate(introspection, bean);
            failOnError(resolutionContext, errors, beanType);
        } else if (bean instanceof Intercepted && definition.hasStereotype(ConfigurationReader.class)) {
            final Collection> executableMethods = definition.getExecutableMethods();
            if (CollectionUtils.isEmpty(executableMethods)) {
                return;
            }
            final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(this, null, bean);
            final Class[] interfaces = beanType.getInterfaces();
            String constructorName;
            if (ArrayUtils.isNotEmpty(interfaces)) {
                constructorName = interfaces[0].getSimpleName();
            } else {
                constructorName = beanType.getSimpleName();
            }
            try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addConstructorNode(constructorName)) {
                for (ExecutableMethod executableMethod : executableMethods) {
                    if (executableMethod.hasAnnotation(Property.class)) {
                        final boolean hasConstraint = executableMethod.hasStereotype(Constraint.class);
                        final boolean isValid = executableMethod.hasStereotype(Valid.class);
                        if (hasConstraint || isValid) {
                            final Object value = executableMethod.invoke(bean);

                            final ReturnType returnType = (ReturnType) executableMethod.getReturnType();

                            try (ValidationPath.ContextualPath ignored1 = context.getCurrentPath().addPropertyNode(executableMethod.getName())) {
                                try (DefaultConstraintValidatorContext.ValidationCloseable ignore2 = context.convertGroups(executableMethod.getAnnotationMetadata())) {

                                    boolean canCascade = true;
                                    for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences()) {
                                        try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {

                                            visitElement(context, bean, returnType.asArgument(), value, canCascade);

                                            if (validation.isFailed()) {
                                                failOnError(resolutionContext, context.getOverallViolations(), beanType);
                                            }
                                        }
                                        canCascade = false;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            failOnError(resolutionContext, context.getOverallViolations(), beanType);
        } else {
            throw new BeanInstantiationException(resolutionContext, "Cannot validate bean [" + beanType.getName() + "]. No bean introspection present. Please add @Introspected.");
        }
    }

    /**
     * looks up a bean introspection for the given object by instance's class or defined class.
     *
     * @param object       The object, never null
     * @param definedClass The defined class of the object, never null
     * @param           The introspection type
     * @return The introspection or null
     */
    @SuppressWarnings({"WeakerAccess", "unchecked"})
    @Nullable
    protected  BeanIntrospection getBeanIntrospection(@NonNull T object,
                                                            @NonNull Class definedClass) {
        //noinspection ConstantConditions
        if (object == null) {
            return null;
        }
        return beanIntrospector.findIntrospection((Class) object.getClass())
            .orElseGet(() -> beanIntrospector.findIntrospection(definedClass).orElse(null));
    }

    /**
     * Looks up a bean introspection for the given object.
     *
     * @param object The object, never null
     * @param     The introspection type
     * @return The introspection or null
     */
    @SuppressWarnings({"WeakerAccess", "unchecked"})
    @Nullable
    protected  BeanIntrospection getBeanIntrospection(@NonNull T object) {
        //noinspection ConstantConditions
        if (object == null) {
            return null;
        }
        if (object instanceof Class) {
            return getBeanIntrospection((Class) object);
        }
        return beanIntrospector.findIntrospection((Class) object.getClass()).orElse(null);
    }

    /**
     * Looks up a bean introspection for the given object.
     *
     * @param type The object type
     * @param   The introspection type
     * @return The introspection or null
     */
    @SuppressWarnings({"WeakerAccess"})
    @Nullable
    protected  BeanIntrospection getBeanIntrospection(@NonNull Class type) {
        return beanIntrospector.findIntrospection(type).orElse(null);
    }

    private  void instrumentPublisherArgumentWithValidation(@NonNull DefaultConstraintValidatorContext context,
                                                                  @NonNull Object[] argumentValues,
                                                                  int argumentIndex,
                                                                  @NonNull Argument publisherArgument,
                                                                  E parameterValue,
                                                                  boolean canCascade) {
        final Publisher publisher = Publishers.convertPublisher(conversionService, parameterValue, Publisher.class);
        DefaultConstraintValidatorContext valueContext = context.copy();

        Publisher objectPublisher;
        if (publisherArgument.isSpecifiedSingle()) {
            objectPublisher = Mono.from(publisher)
                .flatMap(value -> {

                    validatePublishedValue(valueContext, publisherArgument, parameterValue, value, canCascade);

                    return valueContext.getOverallViolations().isEmpty() ? Mono.just(value) :
                        Mono.error(new ConstraintViolationException(valueContext.getOverallViolations()));
                });
        } else {
            objectPublisher = Flux.from(publisher).flatMap(value -> {

                validatePublishedValue(valueContext, publisherArgument, parameterValue, value, canCascade);

                return valueContext.getOverallViolations().isEmpty() ? Flux.just(value) :
                    Flux.error(new ConstraintViolationException(valueContext.getOverallViolations()));
            });
        }
        argumentValues[argumentIndex] = Publishers.convertPublisher(conversionService, objectPublisher, publisherArgument.getType());
    }

    private  void validatePublishedValue(@NonNull DefaultConstraintValidatorContext context,
                                               @NonNull Argument publisherArgument,
                                               E value,
                                               @NonNull Object publisherInstance,
                                               boolean canCascade) {
        // noinspection unchecked
        Argument[] typeParameters = publisherArgument.getTypeParameters();

        if (typeParameters.length == 0) {
            // No validation if no parameters
            return;
        }
        Argument valueArgument = typeParameters[0];

        try (ValidationPath.ContextualPath ignored1 = context.getCurrentPath()
            .addContainerElementNode("", ValidationPath.DefaultContainerContext.ofIterableContainer(value.getClass()))) {
            visitElement(context, context.getRootBean(), valueArgument, publisherInstance, canCascade);
        }
    }

    /**
     * Processes a method argument that is a completion stage. Since the argument cannot be validated
     * at this exact time, the validation is applied to the completion stage.
     */
    private  void instrumentCompletionStageArgumentWithValidation(@NonNull DefaultConstraintValidatorContext context,
                                                                        @NonNull Object[] argumentValues,
                                                                        int argumentIndex,
                                                                        @NonNull Argument completionStageArgument,
                                                                        E parameterValue,
                                                                        boolean canCascade) {
        final CompletionStage completionStage = (CompletionStage) parameterValue;

        Argument valueArgument = (Argument) completionStageArgument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);

        argumentValues[argumentIndex] = instrumentCompletionStage(context.copy(), completionStage, valueArgument, canCascade);
    }

    private  CompletionStage instrumentCompletionStage(DefaultConstraintValidatorContext context,
                                                                CompletionStage completionStage,
                                                                Argument argument,
                                                                boolean canCascade) {
        return completionStage.thenApply(value -> {

            try (ValidationPath.ContextualPath ignored1 = context.getCurrentPath()
                .addContainerElementNode("", ValidationPath.DefaultContainerContext.ofContainer(CompletionStage.class))) {
                visitElement(context, context.getRootBean(), argument, value, canCascade);
            }

            if (!context.getOverallViolations().isEmpty()) {
                throw new ConstraintViolationException(context.getOverallViolations());
            }

            return value;
        });
    }

    private  void validateParametersInternal(@NonNull DefaultConstraintValidatorContext context,
                                                @Nullable T bean,
                                                @NonNull AnnotationMetadata methodAnnotationMetadata,
                                                @NonNull Object[] parameters,
                                                @NonNull Argument[] arguments,
                                                int argLen) {

        List groupSequences;
        if (bean == null) {
            groupSequences = context.findGroupSequences();
        } else {
            BeanIntrospection beanIntrospection = getBeanIntrospection(bean);
            if (beanIntrospection == null) {
                groupSequences = context.findGroupSequences();
            } else {
                groupSequences = context.findGroupSequences(beanIntrospection);
            }
        }
        boolean canCascade = true;
        for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : groupSequences) {
            try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {

                if (methodAnnotationMetadata.hasStereotype(Constraint.class)) {
                    try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addCrossParameterNode()) {
                        validateConstrains(context, bean, Argument.of(Object[].class, methodAnnotationMetadata), parameters);
                    }
                }

                for (int parameterIndex = 0; parameterIndex < argLen; parameterIndex++) {
                    Argument argument = (Argument) arguments[parameterIndex];
                    if (!argument.getAnnotationMetadata().hasAnnotation(ValidatedElement.class)) {
                        continue;
                    }
                    try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addParameterNode(argument.getName(), parameterIndex)) {
                        try (DefaultConstraintValidatorContext.ValidationCloseable ignore = context.convertGroups(argument.getAnnotationMetadata())) {

                            final Class parameterType = argument.getType();

                            Object parameterValue = parameters[parameterIndex];

                            final boolean hasValue = parameterValue != null;

                            final boolean isPublisher = hasValue && Publishers.isConvertibleToPublisher(parameterType);
                            if (isPublisher) {
                                instrumentPublisherArgumentWithValidation(context, parameters, parameterIndex, argument, parameterValue, canCascade);
                                continue;
                            }

                            final boolean isCompletionStage = hasValue && CompletionStage.class.isAssignableFrom(parameterType);
                            if (isCompletionStage) {
                                instrumentCompletionStageArgumentWithValidation(context, parameters, parameterIndex, argument, parameterValue, canCascade);
                                continue;
                            }

                            visitElement(context,
                                bean,
                                argument,
                                parameterValue,
                                canCascade,
                                false
                            );
                        }
                    }
                }

                if (validation.isFailed()) {
                    return;
                }
            }
            canCascade = false;
        }
    }

    private  void doValidate(@NonNull DefaultConstraintValidatorContext context,
                                   @NonNull BeanIntrospection introspection,
                                   @NonNull T object) {
        if (context.isValidated(object)) {
            return;
        }
        boolean canCascade = true;
        try (DefaultConstraintValidatorContext.ValidationCloseable ignore = context.validating(object)) {
            for (DefaultConstraintValidatorContext.ValidationGroup groupSequence : context.findGroupSequences(introspection)) {
                try (DefaultConstraintValidatorContext.GroupsValidation validation = context.withGroupSequence(groupSequence)) {

                    try (ValidationPath.ContextualPath ignore2 = context.getCurrentPath().addBeanNode()) {
                        visitElement(
                            context,
                            object,
                            Argument.of(introspection.getBeanType(), introspection.getAnnotationMetadata()),
                            object,
                            false
                        );
                    }

                    for (BeanProperty property : introspection.getBeanProperties()) {
                        visitProperty(context, object, property, canCascade);
                    }

                    if (validation.isFailed()) {
                        return;
                    }
                }
                canCascade = false;
            }
        }
    }

    private  void visitProperty(DefaultConstraintValidatorContext context,
                                      T object,
                                      BeanProperty property,
                                      boolean canCascade) {
        if (property.isWriteOnly()) {
            return;
        }

        try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addPropertyNode(property.getName())) {
            if (isNotReachable(context, object)) {
                return;
            }
            try (DefaultConstraintValidatorContext.ValidationCloseable ignore = context.convertGroups(property.getAnnotationMetadata())) {
                Object propertyValue;
                try {
                    propertyValue = property.get(object);
                } catch (Exception e) {
                    throw new ValidationException("Failed to get the value of property: " + property.getName());
                }
                visitElement(
                    context,
                    object,
                    property.asArgument(),
                    propertyValue,
                    canCascade
                );
            }
        }
    }

    private  boolean isNotReachable(DefaultConstraintValidatorContext context, T object) {
        ValidationPath currentPath = context.getCurrentPath();
        ValidationPath previousPath = currentPath.previousPath();
        try {
            return !traversableResolver.isReachable(
                    object,
                    currentPath.last(),
                    context.getRootClass(),
                    previousPath,
                    ElementType.FIELD
            );
        } catch (Exception e) {
            throw new ValidationException("Cannot call 'isReachable' on traversableResolver: " + traversableResolver, e);
        }
    }

    private  boolean canCascade(@NonNull DefaultConstraintValidatorContext context,
                                   Object leftBean) {
        try {
            ValidationPath currentPath = context.getCurrentPath();
            ValidationPath previousPath = currentPath.previousPath();
            return traversableResolver.isCascadable(
                    leftBean,
                    currentPath.last(),
                    context.getRootClass(),
                    previousPath,
                    ElementType.FIELD
            );
        } catch (Exception e) {
            throw new ValidationException("Cannot call 'isCascadable' on traversableResolver: " + traversableResolver, e);
        }
    }

    private  void visitElement(DefaultConstraintValidatorContext context,
                                     Object bean,
                                     Argument elementArgument,
                                     E elementValue,
                                     boolean canCascade) {
        visitElement(context,
            bean,
            elementArgument,
            elementArgument.getAnnotationMetadata(),
            elementValue,
            canCascade
        );
    }

    private  void visitElement(DefaultConstraintValidatorContext context,
                                     Object bean,
                                     Argument elementArgument,
                                     AnnotationMetadata annotationMetadata,
                                     E elementValue,
                                     boolean canCascade) {
        visitElement(context,
            bean,
            elementArgument,
            annotationMetadata,
            elementValue,
            canCascade,
            canCascade && annotationMetadata.hasStereotype(Valid.class),
            true
        );
    }

    private  void visitElement(DefaultConstraintValidatorContext context,
                                     Object bean,
                                     Argument elementArgument,
                                     E elementValue,
                                     boolean canCascade,
                                     boolean needsCanCascadeCheck) {
        AnnotationMetadata annotationMetadata = elementArgument.getAnnotationMetadata();
        visitElement(context,
            bean,
            elementArgument,
            annotationMetadata,
            elementValue,
            canCascade,
            needsCanCascadeCheck
        );
    }

    private  void visitElement(DefaultConstraintValidatorContext context,
                                     Object bean,
                                     Argument elementArgument,
                                     AnnotationMetadata annotationMetadata,
                                     E elementValue,
                                     boolean canCascade,
                                     boolean needsCanCascadeCheck) {
        visitElement(context,
            bean,
            elementArgument,
            annotationMetadata,
            elementValue,
            canCascade,
            canCascade && annotationMetadata.hasStereotype(Valid.class),
            needsCanCascadeCheck
        );
    }

    private  void visitElement(DefaultConstraintValidatorContext context,
                                     Object leftBean,
                                     Argument elementArgument,
                                     AnnotationMetadata annotationMetadata,
                                     E elementValue,
                                     boolean canCascade,
                                     boolean hasValid,
                                     boolean needsCanCascadeCheck) {

        List> constraints = getConstraints(context, annotationMetadata);

        if (visitContainer(context, leftBean, elementArgument, annotationMetadata, elementValue, constraints, canCascade)) {
            return;
        }

        if (!constraints.isEmpty()) {
            validateConstrains(context, leftBean, elementArgument, elementValue, constraints);
        }

        if (canCascade && hasValid && elementValue != null) {
            try (DefaultConstraintValidatorContext.ValidationCloseable ignore = context.convertGroups(elementArgument.getAnnotationMetadata())) {
                propagateValidation(context, leftBean, elementArgument, elementValue, needsCanCascadeCheck);
            }
        }
    }

    private  boolean visitContainer(DefaultConstraintValidatorContext context,
                                          Object leftBean,
                                          Argument containerArgument,
                                          AnnotationMetadata annotationMetadata,
                                          E containerValue,
                                          List> constraints,
                                          boolean canCascade) {
        if (!isValidated(containerArgument)) {
            return false;
        }

        boolean isLegacyValid = annotationMetadata.hasAnnotation(Valid.class)
            && (Iterable.class.isAssignableFrom(containerArgument.getType())
            || Map.class.isAssignableFrom(containerArgument.getType())
            || Object[].class.isAssignableFrom(containerArgument.getType())
        );

        final List> skipUnwrappingConstraints = constraints.stream().filter(c -> c.getValueUnwrapping() == ValidateUnwrappedValue.SKIP).toList();
        final List> explicitUnwrappingConstraints = constraints.stream().filter(c -> c.getValueUnwrapping() == ValidateUnwrappedValue.UNWRAP).toList();

        List> valueExtractorDefinitions = valueExtractorRegistry.findValueExtractors(containerArgument.getType());
        if (valueExtractorDefinitions.isEmpty()) {
            if (isLegacyValid && Object[].class.isAssignableFrom(containerArgument.getType())) {
                // Provide a custom legacy value extractor for an array
                containerArgument = (Argument) Argument.of(Object[].class, containerArgument.getAnnotationMetadata());
                valueExtractorDefinitions = List.of(
                    (ValueExtractorDefinition) new ValueExtractorDefinition<>(Object[].class, Object.class, null, false, LEGACY_ARRAY_EXTRACTOR)
                );
            } else {
                if (!explicitUnwrappingConstraints.isEmpty()) {
                    throw new ConstraintDeclarationException("Cannot unwrap the constraint no extractors are present!");
                }
                return false;
            }
        }

        if (!explicitUnwrappingConstraints.isEmpty() && valueExtractorDefinitions.size() > 1) {
            throw new ConstraintDeclarationException("Cannot unwrap the constraint when multiple value extractors are present!");
        }

        long unwrappedCount = valueExtractorDefinitions.stream().filter(ValueExtractorDefinition::unwrapByDefault).count();
        if (unwrappedCount > 1) {
            throw new ConstraintDeclarationException("Multiple unwrap by default value extractors aren't allowed!");
        }

        List> containerElementConstraints;

        if (unwrappedCount > 0) {
            if (valueExtractorDefinitions.size() != unwrappedCount) {
                // Only allow one unwrapped by default value extractor
                valueExtractorDefinitions = valueExtractorDefinitions.stream().filter(ValueExtractorDefinition::unwrapByDefault).toList();
            }
            containerElementConstraints = new ArrayList<>(constraints);
            containerElementConstraints.removeAll(skipUnwrappingConstraints);

            validateConstrains(context, leftBean, containerArgument, containerValue, skipUnwrappingConstraints);
        } else {
            containerElementConstraints = explicitUnwrappingConstraints;

            List> containerConstraints = new ArrayList<>(constraints);
            containerConstraints.removeAll(explicitUnwrappingConstraints);

            validateConstrains(context, leftBean, containerArgument, containerValue, containerConstraints);
        }

        for (ValueExtractorDefinition valueExtractorDefinition : valueExtractorDefinitions) {

            if (isLegacyValid && valueExtractorDefinition.containerType().equals(Map.class) && valueExtractorDefinition.typeArgumentIndex() == 0) {
                // Legacy Map validation only validates values
                continue;
            }

            Integer typeArgumentIndex = valueExtractorDefinition.typeArgumentIndex();
            Argument containerValueArgument;
            Argument[] typeParameters = containerArgument.getTypeParameters();
            if (typeArgumentIndex != null && typeArgumentIndex >= 0 && typeParameters.length > 0 && typeArgumentIndex < typeParameters.length) {
                containerValueArgument = typeParameters[typeArgumentIndex];
            } else {
                containerValueArgument = Argument.of(valueExtractorDefinition.valueType());
                typeArgumentIndex = null;
            }
            if (!isValidated(containerValueArgument) && containerElementConstraints.isEmpty() && !isLegacyValid) {
                continue;
            }

            if (containerValue == null) {
                validateConstrains(context, leftBean, containerValueArgument, null, containerElementConstraints);

                continue;
            }

            ValueExtractor valueExtractor = valueExtractorDefinition.valueExtractor();

            try {
                Integer finalTypeArgumentIndex = typeArgumentIndex;
                Argument finalContainerArgument = containerArgument;
                valueExtractor.extractValues(containerValue, new ValueExtractor.ValueReceiver() {

                    @Override
                    public void value(String nodeName, Object val) {
                        ValidationPath.ContainerContext containerContext = ValidationPath.ContainerContext.value(finalContainerArgument.getType(), finalTypeArgumentIndex);
                        validateContainerValue(context, nodeName, containerContext, val);
                    }

                    @Override
                    public void iterableValue(String nodeName, Object iterableValue) {
                        ValidationPath.ContainerContext containerContext = ValidationPath.ContainerContext.iterable(finalContainerArgument.getType(), finalTypeArgumentIndex);
                        validateContainerValue(context, nodeName, containerContext, iterableValue);
                    }

                    @Override
                    public void indexedValue(String nodeName, int index, Object iterableValue) {
                        ValidationPath.ContainerContext containerContext = ValidationPath.ContainerContext.indexed(finalContainerArgument.getType(), index, finalTypeArgumentIndex);
                        validateContainerValue(context, nodeName, containerContext, iterableValue);

                    }

                    @Override
                    public void keyedValue(String nodeName, Object key, Object val) {
                        ValidationPath.ContainerContext containerContext = ValidationPath.ContainerContext.keyed(finalContainerArgument.getType(), key, finalTypeArgumentIndex);
                        validateContainerValue(context, nodeName, containerContext, val);
                    }

                    private void validateContainerValue(Object value) {
                        validateConstrains(context, leftBean, containerValueArgument, value, containerElementConstraints);

                        visitElement(context,
                            leftBean,
                            containerValueArgument,
                            containerValueArgument.getAnnotationMetadata(),
                            value,
                            canCascade,
                            containerValueArgument.getAnnotationMetadata().hasStereotype(Valid.class) || isLegacyValid,
                            true);
                    }

                    private  void validateContainerValue(DefaultConstraintValidatorContext context,
                                                                 String name,
                                                                 ValidationPath.ContainerContext containerContext,
                                                                 EX value) {
                        if (name != null && !isLegacyValid) {
                            try (ValidationPath.ContextualPath ignored = context.getCurrentPath().addContainerElementNode(name, containerContext)) {
                                validateContainerValue(value);
                            }
                        } else {
                            try (ValidationPath.ContextualPath ignored = context.getCurrentPath().withContainerContext(containerContext)) {
                                validateContainerValue(value);
                            }
                        }
                    }

                });
            } catch (ValidationException e) {
                throw e;
            } catch (Exception e) {
                throw new ValidationException("Exception extracting values using: " + valueExtractor, e);
            }
        }

        return true;
    }

    private  boolean isValidated(Argument containerArgument) {
        return containerArgument.getAnnotationMetadata().hasAnnotation(ValidatedElement.class);
    }

    private  void propagateValidation(DefaultConstraintValidatorContext context,
                                            Object leftBean,
                                            Argument elementType,
                                            E elementValue,
                                            boolean needsCanCascadeCheck) {

        final BeanIntrospection beanIntrospection = getBeanIntrospection(elementValue, elementType.getType());
        if (beanIntrospection == null) {
            // Error if not introspected
            ConstraintDescriptor constraintDescriptor = notIntrospectedConstraint(elementType);
            DefaultConstraintViolation violation = createConstraintViolation(context, leftBean, elementValue, constraintDescriptor);
            context.addViolation(violation);
            return;
        }
        if (!needsCanCascadeCheck || canCascade(context, leftBean)) {
            try (ValidationPath.ContextualPath ignore = context.getCurrentPath().cascaded()) {
                doValidate(context, beanIntrospection, elementValue);
            }
        }
    }

    private  void validateConstrains(DefaultConstraintValidatorContext context,
                                           @Nullable Object leftBean,
                                           @NonNull Argument elementArgument,
                                           @Nullable E elementValue) {
        AnnotationMetadata annotationMetadata = elementArgument.getAnnotationMetadata();
        List> constraints = getConstraints(context, annotationMetadata);
        validateConstrains(context, leftBean, elementArgument, elementValue, constraints);
    }

    private  void validateConstrains(DefaultConstraintValidatorContext context,
                                           @Nullable Object leftBean,
                                           Argument elementArgument,
                                           @NonNull E elementValue,
                                           @NonNull List> constraints) {
        if (constraints.isEmpty()) {
            return;
        }
        ConstraintTarget constraintTarget = context.getCurrentPath().getConstraintTarget();
        for (DefaultConstraintDescriptor constraint : constraints) {
            context.constraint = constraint;
            if (constraint.getValidationAppliesTo() != ConstraintTarget.IMPLICIT && constraint.getValidationAppliesTo() != constraintTarget) {
                continue;
            }
            Class constraintType = constraint.getType();
            List>> validatorClasses = constraint.getConstraintValidatorClasses();
            ConstraintValidator validator = null;
            if (!validatorClasses.isEmpty()) {
                for (Class validatedBy : validatorClasses) {
                    Optional> introspection = beanIntrospector.findIntrospection(validatedBy);
                    if (introspection.isPresent()) {
                        BeanIntrospection beanIntrospection = introspection.get();
                        Set validationTarget = Set.of(beanIntrospection.enumValues(SupportedValidationTarget.class, ValidationTarget.class));
                        if (constraintTarget == ConstraintTarget.PARAMETERS && !validationTarget.contains(ValidationTarget.PARAMETERS)) {
                            continue;
                        }
                        if (constraintTarget != ConstraintTarget.PARAMETERS && (!validationTarget.isEmpty() && !validationTarget.contains(ValidationTarget.ANNOTATED_ELEMENT))) {
                            continue;
                        }

                        jakarta.validation.ConstraintValidator constraintValidator;
                        try {
                            constraintValidator =
                                (jakarta.validation.ConstraintValidator) beanIntrospection.instantiate();
                        } catch (Exception e) {
                            throw new ValidationException("Cannot initialize validator: " + beanIntrospection.getBeanType().getName());
                        }
                        if (constraintValidator instanceof ConstraintValidator) {
                            validator = (ConstraintValidator) constraintValidator;
                        } else {
                            validator = new ConstraintValidator<>() {

                                @Override
                                public void initialize(Annotation constraintAnnotation) {
                                    constraintValidator.initialize(constraintAnnotation);
                                }

                                @Override
                                public boolean isValid(E value, AnnotationValue annotationMetadata, ConstraintValidatorContext context) {
                                    return constraintValidator.isValid(value, context);
                                }
                            };
                            AnnotationValue annotationValue = constraint.getAnnotationValue();
                            MutableAnnotationMetadata mutableAnnotationMetadata = new MutableAnnotationMetadata();
                            mutableAnnotationMetadata.addAnnotation(annotationValue.getAnnotationName(), annotationValue.getValues());

                            try {
                                validator.initialize(mutableAnnotationMetadata.synthesize(Constraint.class));
                            } catch (ValidationException e) {
                                throw e;
                            } catch (Exception e) {
                                throw new ValidationException("Cannot call 'initialize' on: " + validatedBy, e);
                            }
                        }
                    }
                }
                if (validator == null) {
                    continue;
                }
            } else {
                if (constraintTarget == ConstraintTarget.PARAMETERS) {
                    continue;
                }
                validator = constraintValidatorRegistry.findConstraintValidator(constraintType, elementArgument.getType()).orElse(null);
            }
            if (validator == null) {
                throw new UnexpectedTypeException("Cannot find a constraint validator for constraint: " + constraintType.getName() + " and type: " + elementArgument.getType());
            }
            try {
                if (validator.isValid(elementValue, constraint.getAnnotationValue(), context)) {
                    continue;
                }
            } catch (ValidationException e) {
                throw e;
            } catch (Exception e) {
                throw new ValidationException("Cannot call 'isValid' on: " + validator.getClass().getName(), e);
            }

            if (!context.disableDefaultConstraintViolation) {
                DefaultConstraintViolation constraintViolation = createConstraintViolation(context, leftBean, elementValue, constraint);
                context.addViolation(constraintViolation);
            } else if (context.getOverallViolations().isEmpty()) {
                throw new ValidationException("Default violation is disabled and no violations were added");
            }
            context.messageTemplate(null);
            context.constraint = null;
        }
    }

    private  DefaultConstraintViolation createConstraintViolation(DefaultConstraintValidatorContext context,
                                                                        Object leftBean,
                                                                        Object elementValue,
                                                                        ConstraintDescriptor constraint) {
        final String messageTemplate = buildMessageTemplate(context, constraint);
        final String message = messageInterpolator.interpolate(messageTemplate, new MessageInterpolator.Context() {
            @Override
            public ConstraintDescriptor getConstraintDescriptor() {
                return constraint;
            }

            @Override
            public Object getValidatedValue() {
                return elementValue;
            }

            @Override
            public  T unwrap(Class type) {
                throw new ValidationException("Not supported!");
            }
        });

        return new DefaultConstraintViolation<>(
            context.getRootBean(),
            context.getRootClass(),
            leftBean,
            elementValue,
            message,
            messageTemplate,
            new ValidationPath(context.getCurrentPath()),
            constraint,
            context.getExecutableParameterValues(),
            context.getExecutableReturnValue()
        );
    }

    private  boolean isConstraintIncluded(DefaultConstraintValidatorContext context,
                                             DefaultConstraintDescriptor constraint) {
        return context.containsGroup(constraint.getGroups());
    }

    private  List> getConstraints(DefaultConstraintValidatorContext context,
                                                                             AnnotationMetadata annotationMetadata) {
        return annotationMetadata.getAnnotationTypesByStereotype(Constraint.class)
            .stream().
            flatMap(constraintType -> {
                List> annotationValuesByType = annotationMetadata.getAnnotationValuesByType(constraintType);
                if (annotationValuesByType.isEmpty()) {
                    annotationValuesByType = annotationMetadata.getDeclaredAnnotationValuesByType(constraintType);
                }
                return annotationValuesByType.stream()
                    .map(annotationValue -> new DefaultConstraintDescriptor<>(
                        (Class) constraintType,
                        (AnnotationValue) annotationValue,
                        annotationMetadata
                    ))
                    .filter(annotationValue -> {
                        boolean constraintIncluded = isConstraintIncluded(context, annotationValue);
                        return constraintIncluded;
                    });
            })
            .toList();
    }

    private  String buildMessageTemplate(DefaultConstraintValidatorContext context,
                                            ConstraintDescriptor constraint) {
        String messageTemplate = context.getMessageTemplate().orElse(null);
        if (messageTemplate != null) {
            return messageTemplate;
        }
        return constraint.getMessageTemplate();
    }

    private  void failOnError(@NonNull BeanResolutionContext resolutionContext,
                                 Set> errors,
                                 Class beanType) {
        if (!errors.isEmpty()) {
            StringBuilder builder = new StringBuilder()
                .append("Validation failed for bean definition [")
                .append(beanType.getName())
                .append("]\nList of constraint violations:[\n");
            for (ConstraintViolation violation : errors) {
                builder.append('\t').append(violation.getPropertyPath()).append(" - ").append(violation.getMessage()).append('\n');
            }
            builder.append(']');
            throw new BeanInstantiationException(resolutionContext, builder.toString());
        }
    }

    public static  T requireNonNull(String name, T value) {
        if (value == null) {
            throw new IllegalArgumentException("Argument [" + name + "] cannot be null");
        }
        return value;
    }

    public static String requireNonEmpty(String name, String value) {
        if (StringUtils.isEmpty(value)) {
            throw new IllegalArgumentException("Argument [" + name + "] cannot be empty");
        }
        return value;
    }

    private static ConstraintDescriptor notIntrospectedConstraint(Argument notIntrospectedArgument) {
        return new ConstraintDescriptor<>() {

            @Override
            public Annotation getAnnotation() {
                throw new IllegalStateException("Not supported!");
            }

            @Override
            public String getMessageTemplate() {
                return "{" + Introspected.class.getName() + ".message}";
            }

            @Override
            public Set> getGroups() {
                return Set.of();
            }

            @Override
            public Set> getPayload() {
                return Set.of();
            }

            @Override
            public ConstraintTarget getValidationAppliesTo() {
                return ConstraintTarget.IMPLICIT;
            }

            @Override
            public List>> getConstraintValidatorClasses() {
                return List.of();
            }

            @Override
            public Map getAttributes() {
                return Collections.singletonMap("type", notIntrospectedArgument.getType().getName());
            }

            @Override
            public Set> getComposingConstraints() {
                return Set.of();
            }

            @Override
            public boolean isReportAsSingleViolation() {
                return false;
            }

            @Override
            public ValidateUnwrappedValue getValueUnwrapping() {
                return ValidateUnwrappedValue.DEFAULT;
            }

            @Override
            public  U unwrap(Class type) {
                throw new ValidationException("Not supported");
            }
        };
    }

}