package io.micronaut.validation.validator.constraints;
import io.micronaut.context.BeanContext;
import io.micronaut.context.Qualifier;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.beans.BeanProperty;
import io.micronaut.core.beans.BeanWrapper;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.type.Argument;
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.core.util.clhm.ConcurrentLinkedHashMap;
import io.micronaut.inject.qualifiers.Qualifiers;
import io.micronaut.inject.qualifiers.TypeArgumentQualifier;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.ValidationException;
import javax.validation.constraints.*;
import java.lang.annotation.Annotation;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.*;
import java.time.chrono.HijrahDate;
import java.time.chrono.JapaneseDate;
import java.time.chrono.MinguoDate;
import java.time.chrono.ThaiBuddhistDate;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.concurrent.atomic.DoubleAccumulator;
import java.util.concurrent.atomic.DoubleAdder;
@Singleton
@Introspected
public class DefaultConstraintValidators implements ConstraintValidatorRegistry {
private final Map<ValidatorKey, ConstraintValidator> validatorCache = new ConcurrentLinkedHashMap.Builder<ValidatorKey, ConstraintValidator>().initialCapacity(10).maximumWeightedCapacity(40).build();
private final ConstraintValidator<AssertFalse, Boolean> assertFalseValidator =
(value, annotationMetadata, context) -> value == null || !value;
private final ConstraintValidator<AssertTrue, Boolean> assertTrueValidator =
(value, annotationMetadata, context) -> value == null || value;
private final DecimalMaxValidator<CharSequence> decimalMaxValidatorCharSequence =
(value, bigDecimal) -> new BigDecimal(value.toString()).compareTo(bigDecimal);
private final DecimalMaxValidator<Number> decimalMaxValidatorNumber = DefaultConstraintValidators::compareNumber;
private final DecimalMinValidator<CharSequence> decimalMinValidatorCharSequence =
(value, bigDecimal) -> new BigDecimal(value.toString()).compareTo(bigDecimal);
private final DecimalMinValidator<Number> decimalMinValidatorNumber = DefaultConstraintValidators::compareNumber;
private final DigitsValidator<Number> digitsValidatorNumber = value -> {
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
return new BigDecimal(value.toString());
};
private final DigitsValidator<CharSequence> digitsValidatorCharSequence =
value -> new BigDecimal(value.toString());
private final ConstraintValidator<Max, Number> maxNumberValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
final Long max = annotationMetadata.getValue(Long.class).orElseThrow(() ->
new ValidationException("@Max annotation specified without value")
);
if (value instanceof BigInteger) {
return ((BigInteger) value).compareTo(BigInteger.valueOf(max)) < 0;
} else if (value instanceof BigDecimal) {
return ((BigDecimal) value).compareTo(BigDecimal.valueOf(max)) < 0;
}
return value.longValue() <= max;
};
private final ConstraintValidator<Min, Number> minNumberValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
final Long max = annotationMetadata.getValue(Long.class).orElseThrow(() ->
new ValidationException("@Min annotation specified without value")
);
if (value instanceof BigInteger) {
return ((BigInteger) value).compareTo(BigInteger.valueOf(max)) >= 0;
} else if (value instanceof BigDecimal) {
return ((BigDecimal) value).compareTo(BigDecimal.valueOf(max)) >= 0;
}
return value.longValue() >= max;
};
private final ConstraintValidator<Negative, Number> negativeNumberValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
if (value instanceof BigDecimal) {
return ((BigDecimal) value).signum() < 0;
}
if (value instanceof BigInteger) {
return ((BigInteger) value).signum() < 0;
}
if (value instanceof Double ||
value instanceof Float ||
value instanceof DoubleAdder ||
value instanceof DoubleAccumulator) {
return value.doubleValue() < 0;
}
return value.longValue() < 0;
};
private final ConstraintValidator<NegativeOrZero, Number> negativeOrZeroNumberValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
if (value instanceof BigDecimal) {
return ((BigDecimal) value).signum() <= 0;
}
if (value instanceof BigInteger) {
return ((BigInteger) value).signum() <= 0;
}
if (value instanceof Double ||
value instanceof Float ||
value instanceof DoubleAdder ||
value instanceof DoubleAccumulator) {
return value.doubleValue() <= 0;
}
return value.longValue() <= 0;
};
private final ConstraintValidator<Positive, Number> positiveNumberValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
if (value instanceof BigDecimal) {
return ((BigDecimal) value).signum() > 0;
}
if (value instanceof BigInteger) {
return ((BigInteger) value).signum() > 0;
}
if (value instanceof Double ||
value instanceof Float ||
value instanceof DoubleAdder ||
value instanceof DoubleAccumulator) {
return value.doubleValue() > 0;
}
return value.longValue() > 0;
};
private final ConstraintValidator<PositiveOrZero, Number> positiveOrZeroNumberValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
if (value instanceof BigDecimal) {
return ((BigDecimal) value).signum() >= 0;
}
if (value instanceof BigInteger) {
return ((BigInteger) value).signum() >= 0;
}
if (value instanceof Double ||
value instanceof Float ||
value instanceof DoubleAdder ||
value instanceof DoubleAccumulator) {
return value.doubleValue() >= 0;
}
return value.longValue() >= 0;
};
private final ConstraintValidator<NotBlank, CharSequence> notBlankValidator =
(value, annotationMetadata, context) ->
StringUtils.isNotEmpty(value) && value.toString().trim().length() > 0;
private final ConstraintValidator<NotNull, Object> notNullValidator =
(value, annotationMetadata, context) -> value != null;
private final ConstraintValidator<Null, Object> nullValidator =
(value, annotationMetadata, context) -> value == null;
private final ConstraintValidator<NotEmpty, byte[]> notEmptyByteArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, char[]> notEmptyCharArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, boolean[]> notEmptyBooleanArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, double[]> notEmptyDoubleArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, float[]> notEmptyFloatArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, int[]> notEmptyIntArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, long[]> notEmptyLongArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, Object[]> notEmptyObjectArrayValidator = (value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, short[]> notEmptyShortArrayValidator =
(value, annotationMetadata, context) -> value != null && value.length > 0;
private final ConstraintValidator<NotEmpty, CharSequence> notEmptyCharSequenceValidator =
(value, annotationMetadata, context) -> StringUtils.isNotEmpty(value);
private final ConstraintValidator<NotEmpty, Collection> notEmptyCollectionValidator =
(value, annotationMetadata, context) -> CollectionUtils.isNotEmpty(value);
private final ConstraintValidator<NotEmpty, Map> notEmptyMapValidator =
(value, annotationMetadata, context) -> CollectionUtils.isNotEmpty(value);
private final SizeValidator<Object[]> sizeObjectArrayValidator = value -> value.length;
private final SizeValidator<byte[]> sizeByteArrayValidator = value -> value.length;
private final SizeValidator<char[]> sizeCharArrayValidator = value -> value.length;
private final SizeValidator<boolean[]> sizeBooleanArrayValidator = value -> value.length;
private final SizeValidator<double[]> sizeDoubleArrayValidator = value -> value.length;
private final SizeValidator<float[]> sizeFloatArrayValidator = value -> value.length;
private final SizeValidator<int[]> sizeIntArrayValidator = value -> value.length;
private final SizeValidator<long[]> sizeLongArrayValidator = value -> value.length;
private final SizeValidator<short[]> sizeShortArrayValidator = value -> value.length;
private final SizeValidator<CharSequence> sizeCharSequenceValidator = CharSequence::length;
private final SizeValidator<Collection> sizeCollectionValidator = Collection::size;
private final SizeValidator<Map> sizeMapValidator = Map::size;
private final ConstraintValidator<Past, TemporalAccessor> pastTemporalAccessorConstraintValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
Comparable comparable = getNow(value, context.getClockProvider().getClock());
return comparable.compareTo(value) > 0;
};
private final ConstraintValidator<PastOrPresent, TemporalAccessor> pastOrPresentTemporalAccessorConstraintValidator =
(value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
Comparable comparable = getNow(value, context.getClockProvider().getClock());
return comparable.compareTo(value) >= 0;
};
private final ConstraintValidator<Future, TemporalAccessor> futureTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
Comparable comparable = getNow(value, context.getClockProvider().getClock());
return comparable.compareTo(value) < 0;
};
private final ConstraintValidator<FutureOrPresent, TemporalAccessor> futureOrPresentTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> {
if (value == null) {
return true;
}
Comparable comparable = getNow(value, context.getClockProvider().getClock());
return comparable.compareTo(value) <= 0;
};
private final @Nullable BeanContext beanContext;
private final Map<ValidatorKey, ConstraintValidator> localValidators;
public DefaultConstraintValidators() {
this(null);
}
@Inject
protected DefaultConstraintValidators(@Nullable BeanContext beanContext) {
this.beanContext = beanContext;
BeanWrapper<DefaultConstraintValidators> wrapper = BeanWrapper.findWrapper(DefaultConstraintValidators.class, this).orElse(null);
if (wrapper != null) {
final Collection<BeanProperty<DefaultConstraintValidators, Object>> beanProperties = wrapper.getBeanProperties();
Map<ValidatorKey, ConstraintValidator> validatorMap = new HashMap<>(beanProperties.size());
for (BeanProperty<DefaultConstraintValidators, Object> property : beanProperties) {
if (ConstraintValidator.class.isAssignableFrom(property.getType())) {
final Argument[] typeParameters = property.asArgument().getTypeParameters();
if (ArrayUtils.isNotEmpty(typeParameters)) {
final int len = typeParameters.length;
wrapper.getProperty(property.getName(), ConstraintValidator.class).ifPresent(constraintValidator -> {
if (len == 2) {
final Class targetType = ReflectionUtils.getWrapperType(typeParameters[1].getType());
final ValidatorKey key = new ValidatorKey(typeParameters[0].getType(), targetType);
validatorMap.put(key, constraintValidator);
} else if (len == 1) {
if (constraintValidator instanceof SizeValidator) {
final ValidatorKey key = new ValidatorKey(Size.class, typeParameters[0].getType());
validatorMap.put(key, constraintValidator);
} else if (constraintValidator instanceof DigitsValidator) {
final ValidatorKey key = new ValidatorKey(Digits.class, typeParameters[0].getType());
validatorMap.put(key, constraintValidator);
} else if (constraintValidator instanceof DecimalMaxValidator) {
final ValidatorKey key = new ValidatorKey(DecimalMax.class, typeParameters[0].getType());
validatorMap.put(key, constraintValidator);
} else if (constraintValidator instanceof DecimalMinValidator) {
final ValidatorKey key = new ValidatorKey(DecimalMin.class, typeParameters[0].getType());
validatorMap.put(key, constraintValidator);
}
}
});
}
}
}
validatorMap.put(
new ValidatorKey(Pattern.class, CharSequence.class),
new PatternValidator()
);
validatorMap.put(
new ValidatorKey(Email.class, CharSequence.class),
new EmailValidator()
);
this.localValidators = validatorMap;
} else {
this.localValidators = Collections.emptyMap();
}
}
@SuppressWarnings("unchecked")
@NonNull
@Override
public <A extends Annotation, T> Optional<ConstraintValidator<A, T>> findConstraintValidator(@NonNull Class<A> constraintType, @NonNull Class<T> targetType) {
ArgumentUtils.requireNonNull("constraintType", constraintType);
ArgumentUtils.requireNonNull("targetType", targetType);
final ValidatorKey key = new ValidatorKey(constraintType, targetType);
targetType = ReflectionUtils.getWrapperType(targetType);
ConstraintValidator constraintValidator = localValidators.get(key);
if (constraintValidator != null) {
return Optional.of(constraintValidator);
} else {
constraintValidator = validatorCache.get(key);
if (constraintValidator != null) {
return Optional.of(constraintValidator);
} else {
final Qualifier<ConstraintValidator> qualifier = Qualifiers.byTypeArguments(
constraintType,
ReflectionUtils.getWrapperType(targetType)
);
Class<T> finalTargetType = targetType;
final Optional<ConstraintValidator> local = localValidators.entrySet().stream().filter(entry -> {
final ValidatorKey k = entry.getKey();
return TypeArgumentQualifier.areTypesCompatible(
new Class[]{constraintType, finalTargetType},
Arrays.asList(k.constraintType, k.targetType)
);
}
).map(Map.Entry::getValue).findFirst();
if (local.isPresent()) {
validatorCache.put(key, local.get());
return (Optional) local;
} else if (beanContext != null) {
final ConstraintValidator cv = beanContext
.findBean(ConstraintValidator.class, qualifier).orElse(ConstraintValidator.VALID);
validatorCache.put(key, cv);
if (cv != ConstraintValidator.VALID) {
return Optional.of(cv);
}
} else {
final ConstraintValidator cv = findLocalConstraintValidator(constraintType, targetType)
.orElse(ConstraintValidator.VALID);
validatorCache.put(key, cv);
if (cv != ConstraintValidator.VALID) {
return Optional.of(cv);
}
}
}
}
return Optional.empty();
}
public ConstraintValidator<AssertFalse, Boolean> getAssertFalseValidator() {
return assertFalseValidator;
}
public ConstraintValidator<AssertTrue, Boolean> getAssertTrueValidator() {
return assertTrueValidator;
}
public DecimalMaxValidator<CharSequence> getDecimalMaxValidatorCharSequence() {
return decimalMaxValidatorCharSequence;
}
public DecimalMaxValidator<Number> getDecimalMaxValidatorNumber() {
return decimalMaxValidatorNumber;
}
public DecimalMinValidator<CharSequence> getDecimalMinValidatorCharSequence() {
return decimalMinValidatorCharSequence;
}
public DecimalMinValidator<Number> getDecimalMinValidatorNumber() {
return decimalMinValidatorNumber;
}
public DigitsValidator<Number> getDigitsValidatorNumber() {
return digitsValidatorNumber;
}
public DigitsValidator<CharSequence> getDigitsValidatorCharSequence() {
return digitsValidatorCharSequence;
}
public ConstraintValidator<Max, Number> getMaxNumberValidator() {
return maxNumberValidator;
}
public ConstraintValidator<Min, Number> getMinNumberValidator() {
return minNumberValidator;
}
public ConstraintValidator<Negative, Number> getNegativeNumberValidator() {
return negativeNumberValidator;
}
public ConstraintValidator<NegativeOrZero, Number> getNegativeOrZeroNumberValidator() {
return negativeOrZeroNumberValidator;
}
public ConstraintValidator<Positive, Number> getPositiveNumberValidator() {
return positiveNumberValidator;
}
public ConstraintValidator<PositiveOrZero, Number> getPositiveOrZeroNumberValidator() {
return positiveOrZeroNumberValidator;
}
public ConstraintValidator<NotBlank, CharSequence> getNotBlankValidator() {
return notBlankValidator;
}
public ConstraintValidator<NotNull, Object> getNotNullValidator() {
return notNullValidator;
}
public ConstraintValidator<Null, Object> getNullValidator() {
return nullValidator;
}
public ConstraintValidator<NotEmpty, byte[]> getNotEmptyByteArrayValidator() {
return notEmptyByteArrayValidator;
}
public ConstraintValidator<NotEmpty, char[]> getNotEmptyCharArrayValidator() {
return notEmptyCharArrayValidator;
}
public ConstraintValidator<NotEmpty, boolean[]> getNotEmptyBooleanArrayValidator() {
return notEmptyBooleanArrayValidator;
}
public ConstraintValidator<NotEmpty, double[]> getNotEmptyDoubleArrayValidator() {
return notEmptyDoubleArrayValidator;
}
public ConstraintValidator<NotEmpty, float[]> getNotEmptyFloatArrayValidator() {
return notEmptyFloatArrayValidator;
}
public ConstraintValidator<NotEmpty, int[]> getNotEmptyIntArrayValidator() {
return notEmptyIntArrayValidator;
}
public ConstraintValidator<NotEmpty, long[]> getNotEmptyLongArrayValidator() {
return notEmptyLongArrayValidator;
}
public ConstraintValidator<NotEmpty, Object[]> getNotEmptyObjectArrayValidator() {
return notEmptyObjectArrayValidator;
}
public ConstraintValidator<NotEmpty, short[]> getNotEmptyShortArrayValidator() {
return notEmptyShortArrayValidator;
}
public ConstraintValidator<NotEmpty, CharSequence> getNotEmptyCharSequenceValidator() {
return notEmptyCharSequenceValidator;
}
public ConstraintValidator<NotEmpty, Collection> getNotEmptyCollectionValidator() {
return notEmptyCollectionValidator;
}
public ConstraintValidator<NotEmpty, Map> getNotEmptyMapValidator() {
return notEmptyMapValidator;
}
public SizeValidator<Object[]> getSizeObjectArrayValidator() {
return sizeObjectArrayValidator;
}
public SizeValidator<byte[]> getSizeByteArrayValidator() {
return sizeByteArrayValidator;
}
public SizeValidator<char[]> getSizeCharArrayValidator() {
return sizeCharArrayValidator;
}
public SizeValidator<boolean[]> getSizeBooleanArrayValidator() {
return sizeBooleanArrayValidator;
}
public SizeValidator<double[]> getSizeDoubleArrayValidator() {
return sizeDoubleArrayValidator;
}
public SizeValidator<float[]> getSizeFloatArrayValidator() {
return sizeFloatArrayValidator;
}
public SizeValidator<int[]> getSizeIntArrayValidator() {
return sizeIntArrayValidator;
}
public SizeValidator<long[]> getSizeLongArrayValidator() {
return sizeLongArrayValidator;
}
public SizeValidator<short[]> getSizeShortArrayValidator() {
return sizeShortArrayValidator;
}
public SizeValidator<CharSequence> getSizeCharSequenceValidator() {
return sizeCharSequenceValidator;
}
public SizeValidator<Collection> getSizeCollectionValidator() {
return sizeCollectionValidator;
}
public SizeValidator<Map> getSizeMapValidator() {
return sizeMapValidator;
}
public ConstraintValidator<Past, TemporalAccessor> getPastTemporalAccessorConstraintValidator() {
return pastTemporalAccessorConstraintValidator;
}
public ConstraintValidator<PastOrPresent, TemporalAccessor> getPastOrPresentTemporalAccessorConstraintValidator() {
return pastOrPresentTemporalAccessorConstraintValidator;
}
public ConstraintValidator<Future, TemporalAccessor> getFutureTemporalAccessorConstraintValidator() {
return futureTemporalAccessorConstraintValidator;
}
public ConstraintValidator<FutureOrPresent, TemporalAccessor> getFutureOrPresentTemporalAccessorConstraintValidator() {
return futureOrPresentTemporalAccessorConstraintValidator;
}
protected <A extends Annotation, T> Optional<ConstraintValidator> findLocalConstraintValidator(
@NonNull Class<A> constraintType, @NonNull Class<T> targetType) {
return Optional.empty();
}
private Comparable<? extends TemporalAccessor> getNow(TemporalAccessor value, Clock clock) {
if (!(value instanceof Comparable)) {
throw new IllegalArgumentException("TemporalAccessor value must be comparable");
}
if (value instanceof LocalDateTime) {
return LocalDateTime.now(clock);
} else if (value instanceof Instant) {
return Instant.now(clock);
} else if (value instanceof ZonedDateTime) {
return ZonedDateTime.now(clock);
} else if (value instanceof OffsetDateTime) {
return OffsetDateTime.now(clock);
} else if (value instanceof LocalDate) {
return LocalDate.now(clock);
} else if (value instanceof LocalTime) {
return LocalTime.now(clock);
} else if (value instanceof OffsetTime) {
return OffsetTime.now(clock);
} else if (value instanceof MonthDay) {
return MonthDay.now(clock);
} else if (value instanceof Year) {
return Year.now(clock);
} else if (value instanceof YearMonth) {
return YearMonth.now(clock);
} else if (value instanceof HijrahDate) {
return HijrahDate.now(clock);
} else if (value instanceof JapaneseDate) {
return JapaneseDate.now(clock);
} else if (value instanceof ThaiBuddhistDate) {
return ThaiBuddhistDate.now(clock);
} else if (value instanceof MinguoDate) {
return MinguoDate.now(clock);
}
throw new IllegalArgumentException("TemporalAccessor value type not supported: " + value.getClass());
}
private static int compareNumber(@NonNull Number value, @NonNull BigDecimal bigDecimal) {
int result;
if (value instanceof BigDecimal) {
result = ((BigDecimal) value).compareTo(bigDecimal);
} else if (value instanceof BigInteger) {
result = new BigDecimal((BigInteger) value).compareTo(bigDecimal);
} else {
result = BigDecimal.valueOf(value.doubleValue()).compareTo(bigDecimal);
}
return result;
}
protected final class ValidatorKey<A extends Annotation, T> {
private final Class<A> constraintType;
private final Class<T> targetType;
public ValidatorKey(@NonNull Class<A> constraintType, @NonNull Class<T> targetType) {
this.constraintType = constraintType;
this.targetType = targetType;
}
public Class<A> getConstraintType() {
return constraintType;
}
public Class<T> getTargetType() {
return targetType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ValidatorKey<?, ?> key = (ValidatorKey<?, ?>) o;
return constraintType.equals(key.constraintType) &&
targetType.equals(key.targetType);
}
@Override
public int hashCode() {
return Objects.hash(constraintType, targetType);
}
}
}