Validator.java

  1. package no.nav.data.common.validator;

  2. import lombok.Getter;
  3. import lombok.extern.slf4j.Slf4j;
  4. import no.nav.data.common.exceptions.ValidationException;
  5. import no.nav.data.common.storage.StorageService;
  6. import no.nav.data.common.storage.domain.DomainObject;
  7. import no.nav.data.common.storage.domain.TypeRegistration;
  8. import org.apache.commons.lang3.StringUtils;
  9. import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
  10. import org.springframework.util.Assert;

  11. import java.time.LocalDate;
  12. import java.time.format.DateTimeParseException;
  13. import java.util.ArrayList;
  14. import java.util.Collection;
  15. import java.util.List;
  16. import java.util.UUID;
  17. import java.util.concurrent.atomic.AtomicInteger;
  18. import java.util.function.BiConsumer;
  19. import java.util.function.Consumer;
  20. import java.util.function.Function;
  21. import java.util.regex.Pattern;

  22. import static no.nav.data.common.utils.StreamUtils.nullToEmptyList;
  23. import static no.nav.data.common.utils.StreamUtils.safeStream;
  24. import static no.nav.data.common.utils.StringUtils.isUUID;

  25. @Slf4j
  26. public class Validator<T extends Validated> {

  27.     public static final Pattern NAV_IDENT_PATTERN = Pattern.compile("[A-Z][0-9]{6}");

  28.     public static final String DOES_NOT_EXIST = "doesNotExist";
  29.     public static final String ALREADY_EXISTS = "alreadyExist";
  30.     public static final String ILLEGAL_ARGUMENT = "illegalArgument";
  31.     private static final String ERROR_TYPE_MISSING = "fieldIsNullOrMissing";
  32.     private static final String ERROR_TYPE_PATTERN = "fieldWrongFormat";
  33.     private static final String ERROR_TYPE_ENUM = "fieldIsInvalidEnum";
  34.     private static final String ERROR_TYPE_DATE = "fieldIsInvalidDate";
  35.     private static final String ERROR_TYPE_UUID = "fieldIsInvalidUUID";
  36.     public static final String ERROR_MESSAGE_MISSING = "null or missing";
  37.     private static final String ERROR_MESSAGE_PATTERN = "%s is not valid for pattern '%s'";
  38.     private static final String ERROR_MESSAGE_ENUM = "%s was invalid for type %s";
  39.     private static final String ERROR_MESSAGE_DATE = "%s date is not a valid format";
  40.     private static final String ERROR_MESSAGE_UUID = "%s uuid is not a valid format";

  41.     private static final EmailValidator emailValidator = new EmailValidator();
  42.     private static final String EMAIL_DOMAIN = "@nav.no";

  43.     private final List<ValidationError> validationErrors = new ArrayList<>();
  44.     private final String parentField;
  45.     @Getter
  46.     private final T item;
  47.     private DomainObject domainItem;

  48.     public Validator(T item) {
  49.         this.parentField = "";
  50.         this.item = item;
  51.     }

  52.     public Validator(T item, String parentField) {
  53.         this.parentField = StringUtils.appendIfMissing(parentField, ".");
  54.         this.item = item;
  55.     }

  56.     public static <R extends RequestElement> Validator<R> validate(R item, StorageService storage) {
  57.         Validator<R> validator = validate(item);
  58.         UUID uuid = item.getIdAsUUID();
  59.         String typeOfRequest = TypeRegistration.typeOfRequest(item);
  60.         validator.domainItem = uuid != null && storage.exists(uuid, typeOfRequest) ? storage.get(uuid, typeOfRequest) : null;
  61.         validator.validateRepositoryValues(item, validator.domainItem != null);
  62.         return validator;
  63.     }

  64.     public static <R extends Validated> Validator<R> validate(R item) {
  65.         item.format();
  66.         RequestElement requestElement = item instanceof RequestElement re ? re : null;
  67.         if (requestElement != null) {
  68.             Assert.isTrue(requestElement.getUpdate() != null, "request not initialized");
  69.         }
  70.         Validator<R> validator = new Validator<>(item);
  71.         item.validateFieldValues(validator);
  72.         return validator;
  73.     }

  74.     @SuppressWarnings("unchecked")
  75.     public <D extends DomainObject> D getDomainItem() {
  76.         return (D) domainItem;
  77.     }

  78.     @SuppressWarnings("unchecked")
  79.     public <D extends DomainObject> D getDomainItem(Class<D> type) {
  80.         return (D) domainItem;
  81.     }

  82.     public void checkExists(String id, StorageService storage, Class<? extends DomainObject> aClass) {
  83.         if (isUUID(id) && !storage.exists(UUID.fromString(id), aClass)) {
  84.             String type = TypeRegistration.typeOf(aClass);
  85.             addError(type, Validator.DOES_NOT_EXIST, type + " " + id + " does not exist");
  86.         }
  87.     }

  88.     public boolean checkBlank(String fieldName, String fieldValue) {
  89.         if (StringUtils.isBlank(fieldValue)) {
  90.             validationErrors.add(new ValidationError(getFieldName(fieldName), ERROR_TYPE_MISSING, ERROR_MESSAGE_MISSING));
  91.             return true;
  92.         }
  93.         return false;
  94.     }

  95.     public void checkNull(String fieldName, Object fieldValue) {
  96.         if (fieldValue == null) {
  97.             validationErrors.add(new ValidationError(getFieldName(fieldName), ERROR_TYPE_MISSING, ERROR_MESSAGE_MISSING));
  98.         }
  99.     }

  100.     public void checkPatternRequired(String fieldName, String value, Pattern pattern) {
  101.         if (checkBlank(fieldName, value)) {
  102.             return;
  103.         }
  104.         checkPattern(fieldName, value, pattern);
  105.     }

  106.     public void checkPattern(String fieldName, String value, Pattern pattern) {
  107.         if (StringUtils.isBlank(value)) {
  108.             return;
  109.         }
  110.         if (!pattern.matcher(value).matches()) {
  111.             validationErrors.add(new ValidationError(getFieldName(fieldName), ERROR_TYPE_PATTERN, String.format(ERROR_MESSAGE_PATTERN, value, pattern)));
  112.         }
  113.     }

  114.     public <E extends Enum<E>> void checkRequiredEnum(String fieldName, String fieldValue, Class<E> type) {
  115.         if (checkBlank(fieldName, fieldValue)) {
  116.             return;
  117.         }
  118.         checkEnum(fieldName, fieldValue, type);
  119.     }

  120.     public <E extends Enum<E>> void checkEnum(String fieldName, String fieldValue, Class<E> type) {
  121.         if (StringUtils.isBlank(fieldValue)) {
  122.             return;
  123.         }
  124.         try {
  125.             Enum.valueOf(type, fieldValue);
  126.         } catch (IllegalArgumentException e) {
  127.             validationErrors.add(new ValidationError(getFieldName(fieldName), ERROR_TYPE_ENUM, String.format(ERROR_MESSAGE_ENUM, fieldValue, type.getSimpleName())));
  128.         }
  129.     }

  130.     public void checkDate(String fieldName, String fieldValue) {
  131.         if (StringUtils.isBlank(fieldValue)) {
  132.             return;
  133.         }
  134.         try {
  135.             LocalDate.parse(fieldValue);
  136.         } catch (DateTimeParseException e) {
  137.             validationErrors.add(new ValidationError(getFieldName(fieldName), ERROR_TYPE_DATE, String.format(ERROR_MESSAGE_DATE, fieldValue)));
  138.         }
  139.     }

  140.     public void checkUUID(String fieldName, String fieldValue) {
  141.         if (StringUtils.isBlank(fieldValue)) {
  142.             return;
  143.         }
  144.         try {
  145.             //noinspection ResultOfMethodCallIgnored
  146.             UUID.fromString(fieldValue);
  147.         } catch (Exception e) {
  148.             validationErrors.add(new ValidationError(getFieldName(fieldName), ERROR_TYPE_UUID, String.format(ERROR_MESSAGE_UUID, fieldValue)));
  149.         }
  150.     }

  151.     public void checkEmail(String fieldName, String fieldValue) {
  152.         if (!emailValidator.isValid(fieldValue, null)) {
  153.             validationErrors.add(new ValidationError(getFieldName(fieldName), "invalidEmail", "%s is an invalid email".formatted(fieldValue)));
  154.         } else if (!StringUtils.endsWithIgnoreCase(fieldValue, EMAIL_DOMAIN)) {
  155.             validationErrors.add(new ValidationError(getFieldName(fieldName), "invalidEmail", "%s is not an @nav.no email".formatted(fieldValue)));
  156.         }
  157.     }


  158.     public void addError(String fieldName, String errorType, String errorMessage) {
  159.         validationErrors.add(new ValidationError(getFieldName(fieldName), errorType, errorMessage));
  160.     }

  161.     public void checkId(RequestElement request) {
  162.         boolean nullId = request.getId() == null;
  163.         boolean update = request.isUpdate();
  164.         if (update && nullId) {
  165.             validationErrors
  166.                     .add(new ValidationError(getFieldName("id"), "missingIdForUpdate", "Request is missing ID for update"));
  167.         } else if (!update && !nullId) {
  168.             validationErrors.add(new ValidationError(getFieldName("id"), "idForCreate", "Request has ID for create"));
  169.         }
  170.     }

  171.     private String getFieldName(String fieldName) {
  172.         return parentField + fieldName;
  173.     }

  174.     public void validateType(String fieldName, Collection<? extends Validated> fieldValues) {
  175.         AtomicInteger i = new AtomicInteger(0);
  176.         safeStream(fieldValues).forEach(fieldValue -> validateType(String.format("%s[%d]", fieldName, i.getAndIncrement()), fieldValue));
  177.     }

  178.     public void validateType(String fieldName, Validated fieldValue) {
  179.         Validator<Validated> validator = new Validator<>(fieldValue, parentField + fieldName);
  180.         fieldValue.format();
  181.         fieldValue.validateFieldValues(validator);
  182.         validationErrors.addAll(validator.getErrors());
  183.     }

  184.     void validateRepositoryValues(RequestElement request, boolean existInRepository) {
  185.         if (creatingExistingElement(request.isUpdate(), existInRepository)) {
  186.             validationErrors.add(new ValidationError(getFieldName("id"), "creatingExisting",
  187.                     String.format("The %s %s already exists and therefore cannot be created", request.getRequestType(), request.getId())));
  188.         }

  189.         if (updatingNonExistingElement(request.isUpdate(), existInRepository)) {
  190.             validationErrors.add(new ValidationError(getFieldName("id"), "updatingNonExisting",
  191.                     String.format("The %s %s does not exist and therefore cannot be updated", request.getRequestType(), request.getId())));
  192.         }
  193.     }

  194.     private boolean creatingExistingElement(boolean isUpdate, boolean existInRepository) {
  195.         return !isUpdate && existInRepository;
  196.     }

  197.     private boolean updatingNonExistingElement(boolean isUpdate, boolean existInRepository) {
  198.         return isUpdate && !existInRepository;
  199.     }

  200.     public void ifErrorsThrowValidationException() {
  201.         if (!validationErrors.isEmpty()) {
  202.             log.warn("The request was not accepted. The following errors occurred during validation:{}", validationErrors);
  203.             throw new ValidationException(validationErrors, "The request was not accepted. The following errors occurred during validation:");
  204.         }
  205.     }

  206.     public final <R> Validator<T> addValidations(Function<? super T, Collection<R>> extractor, BiConsumer<Validator<T>, R> consumer) {
  207.         Collection<R> subItems = extractor.apply(item);
  208.         nullToEmptyList(subItems).forEach(it -> consumer.accept(this, it));
  209.         return this;
  210.     }

  211.     public final <R> Validator<T> addValidation(Function<? super T, R> extractor, BiConsumer<Validator<T>, R> consumer) {
  212.         R subItem = extractor.apply(item);
  213.         consumer.accept(this, subItem);
  214.         return this;
  215.     }

  216.     public final Validator<T> addValidations(Consumer<Validator<T>> consumer) {
  217.         consumer.accept(this);
  218.         return this;
  219.     }

  220.     public List<ValidationError> getErrors() {
  221.         return validationErrors;
  222.     }
  223. }