Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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.MessageSource;
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.reflect.ClassUtils;
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.ArgumentUtils;
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.MethodReference;
import io.micronaut.inject.annotation.AnnotatedElementValidator;
import io.micronaut.inject.validation.BeanDefinitionValidator;
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.SimpleValueReceiver;
import io.micronaut.validation.validator.extractors.ValueExtractorRegistry;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import javax.validation.ClockProvider;
import javax.validation.Constraint;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ElementKind;
import javax.validation.Path;
import javax.validation.TraversableResolver;
import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.ConstructorDescriptor;
import javax.validation.metadata.ElementDescriptor;
import javax.validation.metadata.MethodDescriptor;
import javax.validation.metadata.MethodType;
import javax.validation.metadata.PropertyDescriptor;
import javax.validation.metadata.Scope;
import javax.validation.valueextraction.ValueExtractor;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Default implementation of the {@link Validator} interface.
*
* @author graemerocher
* @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 List> DEFAULT_GROUPS = Collections.singletonList(Default.class);
private final ConstraintValidatorRegistry constraintValidatorRegistry;
private final ClockProvider clockProvider;
private final ValueExtractorRegistry valueExtractorRegistry;
private final TraversableResolver traversableResolver;
private final ExecutionHandleLocator executionHandleLocator;
private final MessageSource messageSource;
/**
* Default constructor.
*
* @param configuration The validator configuration
*/
protected DefaultValidator(
@NonNull ValidatorConfiguration configuration) {
ArgumentUtils.requireNonNull("configuration", configuration);
this.constraintValidatorRegistry = configuration.getConstraintValidatorRegistry();
this.clockProvider = configuration.getClockProvider();
this.valueExtractorRegistry = configuration.getValueExtractorRegistry();
this.traversableResolver = configuration.getTraversableResolver();
this.executionHandleLocator = configuration.getExecutionHandleLocator();
this.messageSource = configuration.getMessageSource();
}
@SuppressWarnings("unchecked")
@NonNull
@Override
public Set> validate(@NonNull T object, @Nullable Class>... groups) {
ArgumentUtils.requireNonNull("object", object);
final BeanIntrospection introspection = (BeanIntrospection) getBeanIntrospection(object);
if (introspection == null) {
return Collections.emptySet();
}
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, @Nullable Class>... groups) {
if (introspection == null) {
throw new ValidationException("Passed object [" + object + "] cannot be introspected. Please annotate with @Introspected");
}
@SuppressWarnings("unchecked")
final Collection extends BeanProperty> constrainedProperties =
((BeanIntrospection) introspection).getIndexedProperties(Constraint.class);
@SuppressWarnings("unchecked")
final Collection> cascadeProperties =
((BeanIntrospection) introspection).getIndexedProperties(Valid.class);
final List> pojoConstraints = introspection.getAnnotationTypesByStereotype(Constraint.class);
if (CollectionUtils.isNotEmpty(constrainedProperties)
|| CollectionUtils.isNotEmpty(cascadeProperties)
|| CollectionUtils.isNotEmpty(pojoConstraints)) {
DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(object, groups);
Set> overallViolations = new HashSet<>(5);
return doValidate(
introspection,
object,
object,
constrainedProperties,
cascadeProperties,
context,
overallViolations,
pojoConstraints
);
}
return Collections.emptySet();
}
@NonNull
@Override
public Set> validateProperty(
@NonNull T object,
@NonNull String propertyName,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("object", object);
ArgumentUtils.requireNonNull("propertyName", propertyName);
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.isPresent()) {
final BeanProperty constrainedProperty = property.get();
DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(object, groups);
Set overallViolations = new HashSet<>(5);
final Object propertyValue = constrainedProperty.get(object);
@SuppressWarnings("unchecked")
final Class rootBeanClass = (Class) object.getClass();
//noinspection unchecked
validateConstrainedPropertyInternal(
rootBeanClass,
object,
object,
constrainedProperty,
constrainedProperty.getType(),
propertyValue,
context,
overallViolations,
null);
//noinspection unchecked
return Collections.unmodifiableSet(overallViolations);
}
return Collections.emptySet();
}
@NonNull
@Override
public Set> validateValue(
@NonNull Class beanType,
@NonNull String propertyName,
@Nullable Object value,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("beanType", beanType);
ArgumentUtils.requireNonNull("propertyName", propertyName);
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 ValidationException("No property [" + propertyName + "] found on type: " + beanType));
final HashSet overallViolations = new HashSet<>(5);
final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(groups);
try {
context.addPropertyNode(propertyName, null);
//noinspection unchecked
validatePropertyInternal(
beanType,
null,
null,
context,
overallViolations,
beanProperty.getType(),
beanProperty,
value);
} finally {
context.removeLast();
}
//noinspection unchecked
return Collections.unmodifiableSet(overallViolations);
}
@NonNull
@Override
public Set validatedAnnotatedElement(@NonNull AnnotatedElement element, @Nullable Object value) {
ArgumentUtils.requireNonNull("element", element);
if (!element.getAnnotationMetadata().hasStereotype(Constraint.class)) {
return Collections.emptySet();
}
final Set> overallViolations = new HashSet<>(5);
final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext();
try {
context.addPropertyNode(element.getName(), null);
validatePropertyInternal(
null,
element,
element,
context,
overallViolations,
value != null ? value.getClass() : Object.class,
element,
value);
} finally {
context.removeLast();
}
return Collections.unmodifiableSet(overallViolations.stream()
.map(ConstraintViolation::getMessage).collect(Collectors.toSet()));
}
@NonNull
@Override
public T createValid(@NonNull Class beanType, Object... arguments) throws ConstraintViolationException {
ArgumentUtils.requireNonNull("type", beanType);
@SuppressWarnings("unchecked")
final BeanIntrospection introspection = (BeanIntrospection) 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()) {
final T instance = introspection.instantiate(arguments);
final Set> errors = validate(introspection, instance);
if (errors.isEmpty()) {
return instance;
} else {
throw new ConstraintViolationException(errors);
}
}
throw new ConstraintViolationException(constraintViolations);
}
@Override
public BeanDescriptor getConstraintsForClass(Class> clazz) {
return BeanIntrospector.SHARED.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,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("parameterValues", parameterValues);
ArgumentUtils.requireNonNull("object", object);
ArgumentUtils.requireNonNull("method", method);
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(object, groups);
Set overallViolations = new HashSet<>(5);
final Path.Node node = context.addMethodNode(method);
try {
@SuppressWarnings("unchecked")
final Class rootClass = (Class) object.getClass();
validateParametersInternal(
rootClass,
object,
parameterValues,
arguments,
argLen,
context,
overallViolations,
node
);
} finally {
context.removeLast();
}
//noinspection unchecked
return Collections.unmodifiableSet(overallViolations);
}
@NonNull
@Override
public Set> validateParameters(
@NonNull T object, @NonNull
ExecutableMethod method,
@NonNull Collection> argumentValues,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("object", object);
ArgumentUtils.requireNonNull("method", method);
ArgumentUtils.requireNonNull("parameterValues", argumentValues);
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.");
}
DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(object, groups);
Set overallViolations = new HashSet<>(5);
final Path.Node node = context.addMethodNode(method);
try {
@SuppressWarnings("unchecked")
final Class rootClass = (Class) object.getClass();
validateParametersInternal(
rootClass,
object,
argumentValues.stream().map(ArgumentValue::getValue).toArray(),
arguments,
argLen,
context,
overallViolations,
node
);
} finally {
context.removeLast();
}
//noinspection unchecked
return Collections.unmodifiableSet(overallViolations);
}
@NonNull
@Override
public Set> validateParameters(
@NonNull T object,
@NonNull Method method,
@NonNull Object[] parameterValues,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("method", method);
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,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("method", method);
ArgumentUtils.requireNonNull("object", object);
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 object,
@NonNull ExecutableMethod, Object> executableMethod,
@Nullable Object returnValue,
@Nullable Class>... groups) {
final ReturnType returnType = executableMethod.getReturnType();
final Argument returnTypeArgument = returnType.asArgument();
final HashSet overallViolations = new HashSet(3);
@SuppressWarnings("unchecked")
final Class rootBeanClass = (Class) object.getClass();
final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(object, groups);
//noinspection unchecked
validateConstrainedPropertyInternal(
rootBeanClass,
object,
object,
returnTypeArgument,
returnType.getType(),
returnValue,
context,
overallViolations,
null
);
final AnnotationMetadata annotationMetadata = returnTypeArgument.getAnnotationMetadata();
final boolean hasValid = annotationMetadata.isAnnotationPresent(Valid.class);
if (hasValid) {
validateCascadePropertyInternal(context,
rootBeanClass,
object,
object,
returnTypeArgument,
returnValue,
overallViolations);
}
//noinspection unchecked
return overallViolations;
}
private void validateCascadePropertyInternal(DefaultConstraintValidatorContext context,
@NonNull Class rootBeanClass,
@Nullable T rootBean,
Object object,
@NonNull Argument> cascadeProperty,
@Nullable Object propertyValue,
Set overallViolations) {
if (propertyValue != null) {
@SuppressWarnings("unchecked") final Optional extends ValueExtractor> opt = valueExtractorRegistry
.findValueExtractor((Class) cascadeProperty.getType());
opt.ifPresent(valueExtractor -> valueExtractor.extractValues(propertyValue, new ValueExtractor.ValueReceiver() {
@Override
public void value(String nodeName, Object object1) {
}
@Override
public void iterableValue(String nodeName, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootBeanClass,
rootBean,
object,
null,
cascadeProperty,
iterableValue,
overallViolations,
null,
null,
true);
}
@Override
public void indexedValue(String nodeName, int i, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootBeanClass,
rootBean,
object,
null,
cascadeProperty,
iterableValue,
overallViolations,
i,
null,
true);
}
@Override
public void keyedValue(String nodeName, Object key, Object keyedValue) {
if (keyedValue != null && context.validatedObjects.contains(keyedValue)) {
return;
}
cascadeToIterableValue(
context,
rootBeanClass,
rootBean,
object,
null,
cascadeProperty,
keyedValue,
overallViolations,
null,
key,
false
);
}
}));
if (!opt.isPresent() && !context.validatedObjects.contains(propertyValue)) {
try {
// maybe a bean
final Path.Node node = context.addReturnValueNode(cascadeProperty.getName());
final boolean canCascade = canCascade(rootBeanClass, context, propertyValue, node);
if (canCascade) {
cascadeToOne(
rootBeanClass,
rootBean,
object,
context,
overallViolations,
cascadeProperty,
cascadeProperty.getType(),
propertyValue,
null);
}
} finally {
context.removeLast();
}
}
}
}
@NonNull
@Override
public Set> validateConstructorParameters(
@NonNull Constructor extends T> constructor,
@NonNull Object[] parameterValues,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("constructor", constructor);
final Class extends T> declaringClass = constructor.getDeclaringClass();
final BeanIntrospection extends T> introspection = BeanIntrospection.getIntrospection(declaringClass);
return validateConstructorParameters(introspection, parameterValues);
}
@Override
@NonNull
public Set> validateConstructorParameters(
@NonNull BeanIntrospection extends T> introspection,
@NonNull Object[] parameterValues,
@Nullable Class>... groups) {
ArgumentUtils.requireNonNull("introspection", introspection);
final Class extends T> beanType = introspection.getBeanType();
final Argument>[] constructorArguments = introspection.getConstructorArguments();
return validateConstructorParameters(beanType, constructorArguments, parameterValues, groups);
}
@Override
public Set> validateConstructorParameters(Class extends T> beanType, Argument>[] constructorArguments, @NonNull Object[] parameterValues, @Nullable Class>[] 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 = new DefaultConstraintValidatorContext(groups);
Set overallViolations = new HashSet<>(5);
final Path.Node node = context.addConstructorNode(beanType.getSimpleName(), constructorArguments);
try {
validateParametersInternal(
beanType,
null,
parameterValues,
constructorArguments,
argLength,
context,
overallViolations,
node);
} finally {
context.removeLast();
}
//noinspection unchecked
return Collections.unmodifiableSet(overallViolations);
}
@NonNull
@Override
public Set> validateConstructorReturnValue(@NonNull Constructor extends T> constructor, @NonNull T createdObject, @Nullable Class>... groups) {
return validate(createdObject, groups);
}
/**
* 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
* @return The introspection or null
*/
@SuppressWarnings({"WeakerAccess", "unchecked"})
protected @Nullable BeanIntrospection getBeanIntrospection(@NonNull Object object, @NonNull Class> definedClass) {
//noinspection ConstantConditions
if (object == null) {
return null;
}
return BeanIntrospector.SHARED.findIntrospection((Class) object.getClass())
.orElseGet(() -> BeanIntrospector.SHARED.findIntrospection((Class) definedClass).orElse(null));
}
/**
* looks up a bean introspection for the given object.
*
* @param object The object, never null
* @return The introspection or null
*/
@SuppressWarnings({"WeakerAccess", "unchecked"})
protected @Nullable BeanIntrospection getBeanIntrospection(@NonNull Object object) {
//noinspection ConstantConditions
if (object == null) {
return null;
}
if (object instanceof Class) {
return BeanIntrospector.SHARED.findIntrospection((Class) object).orElse(null);
}
return BeanIntrospector.SHARED.findIntrospection((Class) object.getClass()).orElse(null);
}
private void validateParametersInternal(
@NonNull Class rootClass,
@Nullable T object,
@NonNull Object[] parameters,
Argument[] arguments,
int argLen,
DefaultConstraintValidatorContext context,
Set overallViolations,
Path.Node parentNode) {
for (int i = 0; i < argLen; i++) {
Argument argument = arguments[i];
final Class> parameterType = argument.getType();
final AnnotationMetadata annotationMetadata = argument.getAnnotationMetadata();
final boolean hasValid = annotationMetadata.hasStereotype(Validator.ANN_VALID);
final boolean hasConstraint = annotationMetadata.hasStereotype(Validator.ANN_CONSTRAINT);
if (!hasValid && !hasConstraint) {
continue;
}
Object parameterValue = parameters[i];
ValueExtractor valueExtractor = null;
final boolean hasValue = parameterValue != null;
final boolean isValid = hasValue && hasValid;
final boolean isPublisher = hasValue && Publishers.isConvertibleToPublisher(parameterType);
if (isPublisher) {
instrumentPublisherArgumentWithValidation(
rootClass,
object,
parameters,
context,
i,
argument,
parameterType,
annotationMetadata,
parameterValue,
isValid
);
} else {
final boolean isCompletionStage = hasValue && CompletionStage.class.isAssignableFrom(parameterType);
if (isCompletionStage) {
instrumentCompletionStageArgumentWithValidation(
rootClass,
object,
parameters,
context,
i,
argument,
annotationMetadata,
parameterValue,
isValid
);
} else {
if (hasValue) {
//noinspection unchecked
valueExtractor = (ValueExtractor) valueExtractorRegistry.findUnwrapValueExtractor(parameterType).orElse(null);
}
int finalIndex = i;
if (valueExtractor != null) {
valueExtractor.extractValues(parameterValue, (SimpleValueReceiver) (nodeName, unwrappedValue) -> validateParameterInternal(
rootClass,
object,
parameters,
context,
overallViolations,
argument.getName(),
unwrappedValue == null ? Object.class : unwrappedValue.getClass(),
finalIndex,
annotationMetadata,
unwrappedValue
));
} else {
validateParameterInternal(
rootClass,
object,
parameters,
context,
overallViolations,
argument.getName(),
parameterType,
finalIndex,
annotationMetadata,
parameterValue
);
}
if (isValid) {
if (context.validatedObjects.contains(parameterValue)) {
// already validated
continue;
}
// cascade to bean
//noinspection unchecked
valueExtractor = (ValueExtractor) valueExtractorRegistry.findValueExtractor(parameterType).orElse(null);
if (valueExtractor != null) {
valueExtractor.extractValues(parameterValue, new ValueExtractor.ValueReceiver() {
@Override
public void value(String nodeName, Object object1) {
}
@Override
public void iterableValue(String nodeName, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootClass,
object,
parameterValue,
parentNode,
argument,
iterableValue,
overallViolations,
null,
null,
true);
}
@Override
public void indexedValue(String nodeName, int i, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootClass,
object,
parameterValue,
parentNode,
argument,
iterableValue,
overallViolations,
i,
null,
true);
}
@Override
public void keyedValue(String nodeName, Object key, Object keyedValue) {
if (keyedValue != null && context.validatedObjects.contains(keyedValue)) {
return;
}
cascadeToIterableValue(
context,
rootClass,
object,
parameterValue,
parentNode,
argument,
keyedValue,
overallViolations,
null,
key,
false);
}
});
} else {
final BeanIntrospection beanIntrospection = getBeanIntrospection(parameterValue, parameterType);
if (beanIntrospection != null) {
try {
context.addParameterNode(argument.getName(), i);
cascadeToOneIntrospection(
context,
object,
parameterValue,
beanIntrospection,
overallViolations
);
} finally {
context.removeLast();
}
} else {
context.addParameterNode(argument.getName(), i);
overallViolations.add(createIntrospectionConstraintViolation(rootClass, object, context,
parameterType, parameterValue, parameters));
context.removeLast();
}
}
}
}
}
}
}
private void instrumentPublisherArgumentWithValidation(
@NonNull Class rootClass,
@Nullable T object,
@NonNull Object[] argumentValues,
DefaultConstraintValidatorContext context,
int argumentIndex,
Argument argument,
Class> parameterType,
AnnotationMetadata annotationMetadata,
Object parameterValue,
boolean isValid) {
final Publisher publisher = Publishers.convertPublisher(parameterValue, Publisher.class);
PathImpl copied = new PathImpl(context.currentPath);
final Flux finalFlowable = Flux.from(publisher).flatMap(o -> {
DefaultConstraintValidatorContext newContext =
new DefaultConstraintValidatorContext(
object,
copied
);
Set newViolations = new HashSet();
final BeanIntrospection beanIntrospection = !isValid || o == null || ClassUtils.isJavaBasicType(o.getClass()) ? null : getBeanIntrospection(o);
if (beanIntrospection != null) {
try {
context.addParameterNode(argument.getName(), argumentIndex);
cascadeToOneIntrospection(
newContext,
object,
o,
beanIntrospection,
newViolations
);
} finally {
context.removeLast();
}
} else {
final Class t = argument.getFirstTypeVariable().map(Argument::getType).orElse(null);
validateParameterInternal(
rootClass,
object,
argumentValues,
newContext,
newViolations,
argument.getName(),
t != null ? t : Object.class,
argumentIndex,
annotationMetadata,
o
);
}
if (!newViolations.isEmpty()) {
return Flux.error(
new ConstraintViolationException(newViolations)
);
}
return Flux.just(o);
});
argumentValues[argumentIndex] = Publishers.convertPublisher(finalFlowable, parameterType);
}
private void instrumentCompletionStageArgumentWithValidation(
@NonNull Class rootClass,
@Nullable T object,
@NonNull Object[] argumentValues,
DefaultConstraintValidatorContext context,
int argumentIndex,
Argument argument,
AnnotationMetadata annotationMetadata,
Object parameterValue,
boolean isValid) {
final CompletionStage publisher = (CompletionStage) parameterValue;
PathImpl copied = new PathImpl(context.currentPath);
final CompletionStage validatedStage = publisher.thenApply(o -> {
DefaultConstraintValidatorContext newContext =
new DefaultConstraintValidatorContext(
object,
copied
);
Set newViolations = new HashSet();
final BeanIntrospection beanIntrospection = !isValid || o == null || ClassUtils.isJavaBasicType(o.getClass()) ? null : getBeanIntrospection(o);
if (beanIntrospection != null) {
try {
context.addParameterNode(argument.getName(), argumentIndex);
cascadeToOneIntrospection(
newContext,
object,
o,
beanIntrospection,
newViolations
);
} finally {
context.removeLast();
}
} else {
final Class t = argument.getFirstTypeVariable().map(Argument::getType).orElse(null);
validateParameterInternal(
rootClass,
object,
argumentValues,
newContext,
newViolations,
argument.getName(),
t != null ? t : Object.class,
argumentIndex,
annotationMetadata,
o
);
}
if (!newViolations.isEmpty()) {
throw new ConstraintViolationException(newViolations);
}
return o;
});
argumentValues[argumentIndex] = validatedStage;
}
@SuppressWarnings("unchecked")
private void validateParameterInternal(
@NonNull Class rootClass,
@Nullable T object,
@NonNull Object[] argumentValues,
@NonNull DefaultConstraintValidatorContext context,
@NonNull Set overallViolations,
@NonNull String parameterName,
@NonNull Class> parameterType,
int parameterIndex,
@NonNull AnnotationMetadata annotationMetadata,
@Nullable Object parameterValue) {
final String currentMessageTemplate = context.getMessageTemplate().orElse(null);
try {
context.addParameterNode(parameterName, parameterIndex);
final List> constraintTypes =
annotationMetadata.getAnnotationTypesByStereotype(Constraint.class);
// Constraints applied to the parameter
for (Class extends Annotation> constraintType : constraintTypes) {
final ConstraintValidator constraintValidator = constraintValidatorRegistry
.findConstraintValidator(constraintType, parameterType).orElse(null);
if (constraintValidator != null) {
final AnnotationValue extends Annotation> annotationValue =
annotationMetadata.getAnnotation(constraintType);
if (annotationValue != null && !constraintValidator.isValid(parameterValue, annotationValue, context)) {
final String messageTemplate = buildMessageTemplate(context, annotationValue, annotationMetadata);
final Map variables = newConstraintVariables(annotationValue, parameterValue, annotationMetadata);
overallViolations.add(new DefaultConstraintViolation(
object,
rootClass,
object,
parameterValue,
messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(variables)),
messageTemplate,
new PathImpl(context.currentPath),
new DefaultConstraintDescriptor(annotationMetadata, constraintType, annotationValue),
argumentValues));
}
}
}
} finally {
context.removeLast();
context.messageTemplate(currentMessageTemplate);
}
}
@SuppressWarnings("unchecked")
private void validatePojoInternal(@NonNull Class rootClass,
@Nullable T object,
@Nullable Object[] argumentValues,
@NonNull DefaultConstraintValidatorContext context,
@NonNull Set overallViolations,
@NonNull Class> parameterType,
@NonNull Object parameterValue,
Class extends Annotation> pojoConstraint,
AnnotationValue constraintAnnotation) {
final ConstraintValidator constraintValidator = constraintValidatorRegistry
.findConstraintValidator(pojoConstraint, parameterType).orElse(null);
if (constraintValidator != null) {
final String currentMessageTemplate = context.getMessageTemplate().orElse(null);
if (!constraintValidator.isValid(parameterValue, constraintAnnotation, context)) {
BeanIntrospection beanIntrospection = getBeanIntrospection(parameterValue);
if (beanIntrospection == null) {
throw new ValidationException("Passed object [" + parameterValue + "] cannot be introspected. Please annotate with @Introspected");
}
AnnotationMetadata beanAnnotationMetadata = beanIntrospection.getAnnotationMetadata();
AnnotationValue extends Annotation> annotationValue = beanAnnotationMetadata.getAnnotation(pojoConstraint);
final String propertyValue = "";
final String messageTemplate = buildMessageTemplate(context, annotationValue, beanAnnotationMetadata);
final Map variables = newConstraintVariables(annotationValue, propertyValue, beanAnnotationMetadata);
overallViolations.add(new DefaultConstraintViolation(
object,
rootClass,
object,
parameterValue,
messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(variables)),
messageTemplate,
new PathImpl(context.currentPath),
new DefaultConstraintDescriptor(beanAnnotationMetadata, pojoConstraint, annotationValue),
argumentValues));
}
context.messageTemplate(currentMessageTemplate);
}
}
private Set> doValidate(
BeanIntrospection introspection,
@NonNull T rootBean,
@NonNull Object object,
Collection extends BeanProperty> constrainedProperties,
Collection> cascadeProperties,
DefaultConstraintValidatorContext context,
Set overallViolations,
List> pojoConstraints) {
@SuppressWarnings("unchecked")
final Class rootBeanClass = (Class) rootBean.getClass();
for (BeanProperty constrainedProperty : constrainedProperties) {
final Object propertyValue = constrainedProperty.get(object);
//noinspection unchecked
validateConstrainedPropertyInternal(
rootBeanClass,
rootBean,
object,
constrainedProperty,
constrainedProperty.getType(),
propertyValue,
context,
overallViolations,
null);
}
for (Class extends Annotation> pojoConstraint : pojoConstraints) {
validatePojoInternal(
rootBeanClass,
rootBean,
null,
context,
overallViolations,
object.getClass(),
object,
pojoConstraint,
introspection.getAnnotation(pojoConstraint));
}
// now handle cascading validation
for (BeanProperty cascadeProperty : cascadeProperties) {
final Object propertyValue = cascadeProperty.get(object);
if (propertyValue != null) {
@SuppressWarnings("unchecked")
final Optional extends ValueExtractor> opt = valueExtractorRegistry
.findValueExtractor((Class) propertyValue.getClass());
opt.ifPresent(valueExtractor -> valueExtractor.extractValues(propertyValue, new ValueExtractor.ValueReceiver() {
@Override
public void value(String nodeName, Object object1) {
}
@Override
public void iterableValue(String nodeName, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootBeanClass,
rootBean,
object,
cascadeProperty,
iterableValue,
overallViolations,
null,
null,
true);
}
@Override
public void indexedValue(String nodeName, int i, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootBeanClass,
rootBean,
object,
cascadeProperty,
iterableValue,
overallViolations,
i,
null,
true);
}
@Override
public void keyedValue(String nodeName, Object key, Object keyedValue) {
if (keyedValue != null && context.validatedObjects.contains(keyedValue)) {
return;
}
cascadeToIterableValue(
context,
rootBeanClass,
rootBean,
object,
cascadeProperty,
keyedValue,
overallViolations,
null,
key,
false
);
}
}));
if (!opt.isPresent() && !context.validatedObjects.contains(propertyValue)) {
// maybe a bean
final Path.Node node = context.addPropertyNode(cascadeProperty.getName(), null);
try {
final boolean canCascade = canCascade(rootBeanClass, context, propertyValue, node);
if (canCascade) {
cascadeToOne(
rootBeanClass,
rootBean,
object,
context,
overallViolations,
cascadeProperty,
cascadeProperty.getType(),
propertyValue,
null);
}
} finally {
context.removeLast();
}
}
}
}
//noinspection unchecked
return Collections.unmodifiableSet(overallViolations);
}
private boolean canCascade(
Class rootBeanClass,
DefaultConstraintValidatorContext context,
Object propertyValue,
Path.Node node) {
final boolean canCascade = traversableResolver.isCascadable(
propertyValue,
node,
rootBeanClass,
context.currentPath,
ElementType.FIELD
);
final boolean isReachable = traversableResolver.isReachable(
propertyValue,
node,
rootBeanClass,
context.currentPath,
ElementType.FIELD
);
return canCascade && isReachable;
}
private void cascadeToIterableValue(
DefaultConstraintValidatorContext context,
@NonNull Class rootClass,
@Nullable T rootBean,
Object object,
BeanProperty cascadeProperty,
Object iterableValue,
Set overallViolations,
Integer index,
Object key,
boolean isIterable) {
final DefaultPropertyNode container = new DefaultPropertyNode(
cascadeProperty.getName(),
cascadeProperty.getType(),
index,
key,
ElementKind.CONTAINER_ELEMENT,
isIterable
);
cascadeToOne(
rootClass,
rootBean,
object,
context,
overallViolations,
cascadeProperty,
cascadeProperty.getType(),
iterableValue,
container
);
}
private void cascadeToIterableValue(
DefaultConstraintValidatorContext context,
@NonNull Class rootClass,
@Nullable T rootBean,
@Nullable Object object,
Path.Node node,
Argument methodArgument,
Object iterableValue,
Set overallViolations,
Integer index,
Object key,
boolean isIterable) {
if (canCascade(rootClass, context, iterableValue, node)) {
DefaultPropertyNode currentContainerNode = new DefaultPropertyNode(
methodArgument.getName(),
methodArgument.getClass(),
index,
key,
ElementKind.CONTAINER_ELEMENT,
isIterable
);
cascadeToOne(
rootClass,
rootBean,
object,
context,
overallViolations,
methodArgument,
methodArgument.getType(),
iterableValue,
currentContainerNode);
}
}
private void cascadeToOne(
@NonNull Class rootClass,
@Nullable T rootBean,
Object object,
DefaultConstraintValidatorContext context,
Set overallViolations,
AnnotatedElement cascadeProperty,
Class propertyType,
Object propertyValue,
@Nullable DefaultPropertyNode container) {
Class> beanType = Object.class;
if (propertyValue != null) {
beanType = propertyValue.getClass();
} else if (cascadeProperty instanceof BeanProperty) {
Argument argument = ((BeanProperty) cascadeProperty).asArgument();
if (Map.class.isAssignableFrom(argument.getType())) {
Argument[] typeParameters = argument.getTypeParameters();
if (typeParameters.length == 2) {
beanType = typeParameters[1].getType();
}
} else {
beanType = argument
.getFirstTypeVariable()
.map(Argument::getType)
.orElse(null);
}
}
final BeanIntrospection beanIntrospection = getBeanIntrospection(beanType);
AnnotationMetadata annotationMetadata = cascadeProperty.getAnnotationMetadata();
if (beanIntrospection == null && !annotationMetadata.hasStereotype(Constraint.class)) {
// error: only has @Valid but the propertyValue class is not @Introspected
overallViolations.add(createIntrospectionConstraintViolation(
rootClass, rootBean, context, beanType, propertyValue));
return;
}
if (beanIntrospection != null) {
if (container != null) {
context.addPropertyNode(container.getName(), container);
}
try {
cascadeToOneIntrospection(
context,
rootBean,
propertyValue,
beanIntrospection,
overallViolations);
} finally {
if (container != null) {
context.removeLast();
}
}
} else {
// try apply cascade rules to actual property
//noinspection unchecked
validateConstrainedPropertyInternal(
rootClass,
rootBean,
object,
cascadeProperty,
propertyType,
propertyValue,
context,
overallViolations,
container
);
}
}
private void cascadeToOneIntrospection(DefaultConstraintValidatorContext context, T rootBean, Object bean, BeanIntrospection beanIntrospection, Set overallViolations) {
context.validatedObjects.add(bean);
final Collection> cascadeConstraints =
beanIntrospection.getIndexedProperties(Constraint.class);
final Collection> cascadeNestedProperties =
beanIntrospection.getIndexedProperties(Valid.class);
final List> pojoConstraints =
beanIntrospection.getAnnotationMetadata().getAnnotationTypesByStereotype(Constraint.class);
if (CollectionUtils.isNotEmpty(cascadeConstraints) ||
CollectionUtils.isNotEmpty(cascadeNestedProperties) ||
CollectionUtils.isNotEmpty(pojoConstraints)
) {
doValidate(
beanIntrospection,
rootBean,
bean,
cascadeConstraints,
cascadeNestedProperties,
context,
overallViolations,
pojoConstraints
);
}
}
private void validateConstrainedPropertyInternal(
@NonNull Class rootBeanClass,
@Nullable T rootBean,
@NonNull Object object,
@NonNull AnnotatedElement constrainedProperty,
@NonNull Class propertyType,
@Nullable Object propertyValue,
DefaultConstraintValidatorContext context,
Set> overallViolations,
@Nullable DefaultPropertyNode container) {
context.addPropertyNode(
constrainedProperty.getName(), container
);
final String currentMessageTemplate = context.getMessageTemplate().orElse(null);
validatePropertyInternal(
rootBeanClass,
rootBean,
object,
context,
overallViolations,
propertyType,
constrainedProperty,
propertyValue
);
context.removeLast();
context.messageTemplate(currentMessageTemplate);
}
private void validatePropertyInternal(
@Nullable Class rootBeanClass,
@Nullable T rootBean,
@Nullable Object object,
@NonNull DefaultConstraintValidatorContext context,
@NonNull Set> overallViolations,
@NonNull Class propertyType,
@NonNull AnnotatedElement constrainedProperty,
@Nullable Object propertyValue) {
final AnnotationMetadata annotationMetadata = constrainedProperty.getAnnotationMetadata();
final List> constraintTypes = annotationMetadata.getAnnotationTypesByStereotype(Constraint.class);
for (Class extends Annotation> constraintType : constraintTypes) {
ValueExtractor valueExtractor = null;
if (propertyValue != null && !annotationMetadata.hasAnnotation(Valid.class)) {
//noinspection unchecked
valueExtractor = valueExtractorRegistry.findUnwrapValueExtractor((Class) propertyValue.getClass())
.orElse(null);
}
if (valueExtractor != null) {
valueExtractor.extractValues(propertyValue, (SimpleValueReceiver) (nodeName, extractedValue) -> valueConstraintOnProperty(
rootBeanClass,
rootBean,
object,
context,
overallViolations,
constrainedProperty,
propertyType,
extractedValue,
constraintType
));
} else {
valueConstraintOnProperty(
rootBeanClass,
rootBean,
object,
context,
overallViolations,
constrainedProperty,
propertyType,
propertyValue,
constraintType
);
}
}
}
@SuppressWarnings("unchecked")
private void valueConstraintOnProperty(
@Nullable Class rootBeanClass,
@Nullable T rootBean,
@Nullable Object object,
DefaultConstraintValidatorContext context,
Set> overallViolations,
AnnotatedElement constrainedProperty,
Class propertyType,
@Nullable Object propertyValue,
Class extends Annotation> constraintType) {
final AnnotationMetadata annotationMetadata = constrainedProperty
.getAnnotationMetadata();
final List extends AnnotationValue extends Annotation>> annotationValues = annotationMetadata
.getAnnotationValuesByType(constraintType);
Set> constraints = new HashSet<>(3);
for (Class> group : context.groups) {
for (AnnotationValue extends Annotation> annotationValue : annotationValues) {
final Class>[] classValues = annotationValue.classValues("groups");
if (ArrayUtils.isEmpty(classValues)) {
if (context.groups == DEFAULT_GROUPS || group == Default.class) {
constraints.add(annotationValue);
}
} else {
final List constraintGroups = Arrays.asList(classValues);
if (constraintGroups.contains(group)) {
constraints.add(annotationValue);
}
}
}
}
@SuppressWarnings("unchecked") final Class targetType = propertyValue != null ? (Class) propertyValue.getClass() : propertyType;
final ConstraintValidator extends Annotation, Object> validator = constraintValidatorRegistry
.findConstraintValidator(constraintType, targetType).orElse(null);
if (validator != null) {
for (AnnotationValue annotationValue : constraints) {
//noinspection unchecked
if (!validator.isValid(propertyValue, annotationValue, context)) {
final String messageTemplate = buildMessageTemplate(context, annotationValue, annotationMetadata);
Map variables = newConstraintVariables(annotationValue, propertyValue, annotationMetadata);
//noinspection unchecked
overallViolations.add(
new DefaultConstraintViolation(
rootBean,
rootBeanClass,
object,
propertyValue,
messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(variables)),
messageTemplate,
new PathImpl(context.currentPath),
new DefaultConstraintDescriptor(annotationMetadata, constraintType, annotationValue))
);
}
}
}
}
private Map newConstraintVariables(AnnotationValue annotationValue, @Nullable Object propertyValue, AnnotationMetadata annotationMetadata) {
final Map, ?> values = annotationValue.getValues();
int initSize = (int) Math.ceil(values.size() / 0.75);
Map variables = new LinkedHashMap<>(initSize);
for (Map.Entry, ?> entry : values.entrySet()) {
variables.put(entry.getKey().toString(), entry.getValue());
}
variables.put("validatedValue", propertyValue);
final Map defaultValues = annotationMetadata.getDefaultValues(annotationValue.getAnnotationName());
for (Map.Entry entry : defaultValues.entrySet()) {
final String n = entry.getKey();
if (!variables.containsKey(n)) {
final Object v = entry.getValue();
if (v != null) {
variables.put(n, v);
}
}
}
return variables;
}
private String buildMessageTemplate(final DefaultConstraintValidatorContext context, final AnnotationValue> annotationValue,
final AnnotationMetadata annotationMetadata) {
return context.getMessageTemplate()
.orElseGet(() -> annotationValue.stringValue("message")
.orElseGet(() -> annotationMetadata.getDefaultValue(annotationValue.getAnnotationName(), "message", String.class)
.orElse("{" + annotationValue.getAnnotationName() + ".message}")));
}
@NonNull
@Override
public Publisher validatePublisher(@NonNull Publisher publisher, Class>... groups) {
ArgumentUtils.requireNonNull("publisher", publisher);
final Publisher reactiveSequence = Publishers.convertPublisher(publisher, Publisher.class);
return Flux.from(reactiveSequence).flatMap(object -> {
final Set> constraintViolations = validate(object, groups);
if (!constraintViolations.isEmpty()) {
return Flux.error(new ConstraintViolationException(constraintViolations));
}
return Flux.just(object);
});
}
@NonNull
@Override
public CompletionStage validateCompletionStage(@NonNull CompletionStage completionStage, Class>... groups) {
ArgumentUtils.requireNonNull("completionStage", completionStage);
return completionStage.thenApply(t -> {
final Set> constraintViolations = validate(t, groups);
if (!constraintViolations.isEmpty()) {
throw new ConstraintViolationException(constraintViolations);
}
return t;
});
}
@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);
final Class parameterType = argument.getType();
final Class rootClass = injectionPoint.getDeclaringBean().getBeanType();
DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(value);
Set overallViolations = new HashSet<>(5);
if (hasConstraint) {
final Path.Node parentNode = context.addConstructorNode(rootClass.getName(), injectionPoint.getDeclaringBean().getConstructor().getArguments());
ValueExtractor valueExtractor = (ValueExtractor) valueExtractorRegistry.findValueExtractor(parameterType).orElse(null);
if (valueExtractor != null) {
valueExtractor.extractValues(value, new ValueExtractor.ValueReceiver() {
@Override
public void value(String nodeName, Object object1) {
}
@Override
public void iterableValue(String nodeName, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootClass,
null,
value,
parentNode,
argument,
iterableValue,
overallViolations,
null,
null,
true);
}
@Override
public void indexedValue(String nodeName, int i, Object iterableValue) {
if (iterableValue != null && context.validatedObjects.contains(iterableValue)) {
return;
}
cascadeToIterableValue(
context,
rootClass,
null,
value,
parentNode,
argument,
iterableValue,
overallViolations,
i,
null,
true);
}
@Override
public void keyedValue(String nodeName, Object key, Object keyedValue) {
if (keyedValue != null && context.validatedObjects.contains(keyedValue)) {
return;
}
cascadeToIterableValue(
context,
rootClass,
null,
value,
parentNode,
argument,
keyedValue,
overallViolations,
null,
key,
false);
}
});
} else {
validateParameterInternal(
rootClass,
null,
ArrayUtils.EMPTY_OBJECT_ARRAY,
context,
overallViolations,
argument.getName(),
parameterType,
index,
annotationMetadata,
value
);
}
context.removeLast();
} else if (hasValid && value != null) {
final BeanIntrospection beanIntrospection = getBeanIntrospection(value, parameterType);
if (beanIntrospection != null) {
try {
context.addParameterNode(argument.getName(), index);
cascadeToOneIntrospection(
context,
null,
value,
beanIntrospection,
overallViolations
);
} finally {
context.removeLast();
}
}
}
failOnError(resolutionContext, overallViolations, rootClass);
}
@Override
public void validateBean(@NonNull BeanResolutionContext resolutionContext, @NonNull BeanDefinition definition, @NonNull T bean) throws BeanInstantiationException {
final BeanIntrospection introspection = (BeanIntrospection) getBeanIntrospection(bean);
if (introspection != null) {
Set> errors = validate(introspection, bean);
final Class> beanType = bean.getClass();
failOnError(resolutionContext, errors, beanType);
} else if (bean instanceof Intercepted && definition.hasStereotype(ConfigurationReader.class)) {
final Collection> executableMethods = definition.getExecutableMethods();
if (CollectionUtils.isNotEmpty(executableMethods)) {
Set> errors = new HashSet<>();
final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext(bean);
final Class beanType = definition.getBeanType();
final Class>[] interfaces = beanType.getInterfaces();
if (ArrayUtils.isNotEmpty(interfaces)) {
context.addConstructorNode(interfaces[0].getSimpleName());
} else {
context.addConstructorNode(beanType.getSimpleName());
}
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);
validateConstrainedPropertyInternal(
beanType,
bean,
bean,
executableMethod,
executableMethod.getReturnType().getType(),
value,
context,
errors,
null
);
}
}
}
failOnError(resolutionContext, errors, beanType);
}
}
}
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());
}
}
@NonNull
private DefaultConstraintViolation createIntrospectionConstraintViolation(
@NonNull Class rootClass,
T object,
DefaultConstraintValidatorContext context,
Class> parameterType,
Object parameterValue,
Object... parameters
) {
final String messageTemplate = context.getMessageTemplate()
.orElseGet(() -> "{" + Introspected.class.getName() + ".message}");
return new DefaultConstraintViolation<>(object, rootClass, object, parameterValue,
messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(Collections.singletonMap("type", parameterType.getName()))),
messageTemplate, new PathImpl(context.currentPath), null, parameters);
}
/**
* The context object.
*/
private final class DefaultConstraintValidatorContext implements ConstraintValidatorContext {
final Set validatedObjects = new HashSet<>(20);
final PathImpl currentPath;
final List> groups;
String messageTemplate = null;
private DefaultConstraintValidatorContext(T object, Class>... groups) {
this(object, new PathImpl(), groups);
}
private DefaultConstraintValidatorContext(T object, PathImpl path, Class>... groups) {
if (object != null) {
validatedObjects.add(object);
}
if (ArrayUtils.isNotEmpty(groups)) {
sanityCheckGroups(groups);
List> groupList = new ArrayList<>();
for (Class> group: groups) {
addInheritedGroups(group, groupList);
}
this.groups = Collections.unmodifiableList(groupList);
} else {
this.groups = DEFAULT_GROUPS;
}
this.currentPath = path != null ? path : new PathImpl();
}
private DefaultConstraintValidatorContext(Class>... groups) {
this(null, groups);
}
private void sanityCheckGroups(Class>[] groups) {
ArgumentUtils.requireNonNull("groups", groups);
for (Class> clazz : groups) {
if (clazz == null) {
throw new IllegalArgumentException("Validation groups must be non-null");
}
if (!clazz.isInterface()) {
throw new IllegalArgumentException(
"Validation groups must be interfaces. " + clazz.getName() + " is not.");
}
}
}
private void addInheritedGroups(Class> group, List> groups) {
if (!groups.contains(group)) {
groups.add(group);
}
for (Class> inheritedGroup : group.getInterfaces()) {
addInheritedGroups(inheritedGroup, groups);
}
}
@NonNull
@Override
public ClockProvider getClockProvider() {
return clockProvider;
}
@Nullable
@Override
public Object getRootBean() {
return validatedObjects.isEmpty() ? null : validatedObjects.iterator().next();
}
@Override
public void messageTemplate(@Nullable final String messageTemplate) {
this.messageTemplate = messageTemplate;
}
Optional getMessageTemplate() {
return Optional.ofNullable(messageTemplate);
}
Path.Node addPropertyNode(String name, @Nullable DefaultPropertyNode container) {
final DefaultPropertyNode node;
if (container != null) {
node = new DefaultPropertyNode(
name, container
);
} else {
node = new DefaultPropertyNode(name, null, null, null, ElementKind.PROPERTY, false);
}
currentPath.nodes.add(node);
return node;
}
Path.Node addReturnValueNode(String name) {
final DefaultReturnValueNode returnValueNode;
returnValueNode = new DefaultReturnValueNode(name);
currentPath.nodes.add(returnValueNode);
return returnValueNode;
}
void removeLast() {
currentPath.nodes.removeLast();
}
Path.Node addMethodNode(MethodReference, ?> reference) {
final DefaultMethodNode methodNode = new DefaultMethodNode(reference);
currentPath.nodes.add(methodNode);
return methodNode;
}
void addParameterNode(String name, int index) {
final DefaultParameterNode node;
node = new DefaultParameterNode(
name, index
);
currentPath.nodes.add(node);
}
Path.Node addConstructorNode(String simpleName, Argument>... constructorArguments) {
final DefaultConstructorNode node = new DefaultConstructorNode(new MethodReference() {
@Override
public Argument[] getArguments() {
return constructorArguments;
}
@Override
public Method getTargetMethod() {
return null;
}
@Override
public ReturnType getReturnType() {
return null;
}
@Override
public Class getDeclaringType() {
return null;
}
@Override
public String getMethodName() {
return simpleName;
}
});
currentPath.nodes.add(node);
return node;
}
}
/**
* Path implementation.
*/
private final class PathImpl implements Path {
final Deque nodes;
/**
* Copy constructor.
*
* @param nodes The nodes
*/
private PathImpl(PathImpl nodes) {
this.nodes = new LinkedList<>(nodes.nodes);
}
private PathImpl() {
this.nodes = new LinkedList<>();
}
@Override
public Iterator iterator() {
return nodes.iterator();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
final Iterator i = nodes.iterator();
while (i.hasNext()) {
final Node node = i.next();
builder.append(node.getName());
if (node.getKind() == ElementKind.CONTAINER_ELEMENT) {
final Integer index = node.getIndex();
if (index != null) {
builder.append('[').append(index).append(']');
} else {
final Object key = node.getKey();
if (key != null) {
builder.append('[').append(key).append(']');
} else {
builder.append("[]");
}
}
}
if (i.hasNext()) {
builder.append('.');
}
}
return builder.toString();
}
}
/**
* Constructor node.
*/
private final class DefaultConstructorNode extends DefaultMethodNode implements Path.ConstructorNode {
public DefaultConstructorNode(MethodReference methodReference) {
super(methodReference);
}
@Override
public ElementKind getKind() {
return ElementKind.CONSTRUCTOR;
}
}
/**
* Method node implementation.
*/
private class DefaultMethodNode implements Path.MethodNode {
private final MethodReference, ?> methodReference;
public DefaultMethodNode(MethodReference, ?> methodReference) {
this.methodReference = methodReference;
}
@Override
public List> getParameterTypes() {
return Arrays.asList(methodReference.getArgumentTypes());
}
@Override
public String getName() {
return methodReference.getMethodName();
}
@Override
public boolean isInIterable() {
return false;
}
@Override
public Integer getIndex() {
return null;
}
@Override
public Object getKey() {
return null;
}
@Override
public ElementKind getKind() {
return ElementKind.METHOD;
}
@Override
public String toString() {
return getName();
}
@Override
public T as(Class nodeType) {
throw new UnsupportedOperationException("Unwrapping is unsupported by this implementation");
}
}
/**
* Method node implementation.
*/
private final class DefaultParameterNode extends DefaultPropertyNode implements Path.ParameterNode {
private final int parameterIndex;
DefaultParameterNode(@NonNull String name, int parameterIndex) {
super(name, null, null, null, ElementKind.PARAMETER, false);
this.parameterIndex = parameterIndex;
}
@Override
public ElementKind getKind() {
return ElementKind.PARAMETER;
}
@Override
public T as(Class nodeType) {
throw new UnsupportedOperationException("Unwrapping is unsupported by this implementation");
}
@Override
public int getParameterIndex() {
return parameterIndex;
}
}
/**
* Default Return value node implementation.
*/
private class DefaultReturnValueNode implements Path.ReturnValueNode {
private final String name;
private final Integer index;
private final Object key;
private final ElementKind kind;
private final boolean isInIterable;
public DefaultReturnValueNode(String name,
Integer index,
Object key,
ElementKind kind,
boolean isInIterable) {
this.name = name;
this.index = index;
this.key = key;
this.kind = kind;
this.isInIterable = isInIterable;
}
public DefaultReturnValueNode(String name) {
this(name, null, null, ElementKind.RETURN_VALUE, false);
}
@Override
public String getName() {
return name;
}
@Override
public Integer getIndex() {
return index;
}
@Override
public Object getKey() {
return key;
}
@Override
public ElementKind getKind() {
return kind;
}
@Override
public boolean isInIterable() {
return isInIterable;
}
@Override
public T as(Class nodeType) {
throw new UnsupportedOperationException("Unwrapping is unsupported by this implementation");
}
}
/**
* Default property node impl.
*/
private class DefaultPropertyNode implements Path.PropertyNode {
private final Class> containerClass;
private final String name;
private final Integer index;
private final Object key;
private final ElementKind kind;
private final boolean isIterable;
DefaultPropertyNode(
@NonNull String name,
@Nullable Class> containerClass,
@Nullable Integer index,
@Nullable Object key,
@NonNull ElementKind kind,
boolean isIterable) {
this.containerClass = containerClass;
this.name = name;
this.index = index;
this.key = key;
this.kind = kind;
this.isIterable = isIterable || index != null;
}
DefaultPropertyNode(
@NonNull String name,
@NonNull DefaultPropertyNode parent
) {
this(name, parent.containerClass, parent.getIndex(), parent.getKey(), ElementKind.CONTAINER_ELEMENT, parent.isInIterable());
}
@Override
public Class> getContainerClass() {
return containerClass;
}
@Override
public Integer getTypeArgumentIndex() {
return null;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isInIterable() {
return isIterable;
}
@Override
public Integer getIndex() {
return index;
}
@Override
public Object getKey() {
return key;
}
@Override
public ElementKind getKind() {
return kind;
}
@Override
public String toString() {
return getName();
}
@Override
public T as(Class nodeType) {
throw new UnsupportedOperationException("Unwrapping is unsupported by this implementation");
}
}
/**
* Default implementation of {@link ConstraintViolation}.
*
* @param The bean type.
*/
private final class DefaultConstraintViolation implements ConstraintViolation {
private final T rootBean;
private final Object invalidValue;
private final String message;
private final String messageTemplate;
private final Path path;
private final Class rootBeanClass;
private final Object leafBean;
private final ConstraintDescriptor> constraintDescriptor;
private final Object[] executableParams;
private DefaultConstraintViolation(
@Nullable T rootBean,
@Nullable Class rootBeanClass,
Object leafBean,
Object invalidValue,
String message,
String messageTemplate,
Path path,
ConstraintDescriptor> constraintDescriptor,
Object... executableParams) {
this.rootBean = rootBean;
this.rootBeanClass = rootBeanClass;
this.invalidValue = invalidValue;
this.message = message;
this.messageTemplate = messageTemplate;
this.path = path;
this.leafBean = leafBean;
this.constraintDescriptor = constraintDescriptor;
this.executableParams = executableParams;
}
@Override
public String getMessage() {
return message;
}
@Override
public String getMessageTemplate() {
return messageTemplate;
}
@Override
public T getRootBean() {
return rootBean;
}
@Override
public Class getRootBeanClass() {
return rootBeanClass;
}
@Override
public Object getLeafBean() {
return leafBean;
}
@Override
public Object[] getExecutableParameters() {
if (executableParams != null) {
return executableParams;
} else {
return ArrayUtils.EMPTY_OBJECT_ARRAY;
}
}
@Override
public Object getExecutableReturnValue() {
return null;
}
@Override
public Path getPropertyPath() {
return path;
}
@Override
public Object getInvalidValue() {
return invalidValue;
}
@Override
public ConstraintDescriptor> getConstraintDescriptor() {
return constraintDescriptor;
}
@Override
public U unwrap(Class type) {
throw new UnsupportedOperationException("Unwrapping is unsupported by this implementation");
}
@Override
public String toString() {
return "DefaultConstraintViolation{" +
"rootBean=" + rootBeanClass +
", invalidValue=" + invalidValue +
", path=" + path +
'}';
}
}
/**
* An empty descriptor with no constraints.
*/
private final class EmptyDescriptor implements BeanDescriptor, ElementDescriptor.ConstraintFinder {
private final Class> elementClass;
EmptyDescriptor(Class> elementClass) {
this.elementClass = elementClass;
}
@Override
public boolean isBeanConstrained() {
return false;
}
@Override
public PropertyDescriptor getConstraintsForProperty(String propertyName) {
return null;
}
@Override
public Set getConstrainedProperties() {
return Collections.emptySet();
}
@Override
public MethodDescriptor getConstraintsForMethod(String methodName, Class>... parameterTypes) {
return null;
}
@Override
public Set getConstrainedMethods(MethodType methodType, MethodType... methodTypes) {
return Collections.emptySet();
}
@Override
public ConstructorDescriptor getConstraintsForConstructor(Class>... parameterTypes) {
return null;
}
@Override
public Set getConstrainedConstructors() {
return Collections.emptySet();
}
@Override
public boolean hasConstraints() {
return false;
}
@Override
public Class> getElementClass() {
return elementClass;
}
@Override
public ConstraintFinder unorderedAndMatchingGroups(Class>... groups) {
return this;
}
@Override
public ConstraintFinder lookingAt(Scope scope) {
return this;
}
@Override
public ConstraintFinder declaredOn(ElementType... types) {
return this;
}
@Override
public Set> getConstraintDescriptors() {
return Collections.emptySet();
}
@Override
public ConstraintFinder findConstraints() {
return this;
}
}
}