简体   繁体   中英

How can I conditionally apply validation groups in Spring Data REST?

I have a Spring Data REST project with an entity type with conditional validation based on a property of the entity. I want to enable certain validations using validation groups when that property is set to a specific value.

As a concrete example, take the following entity class:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;

@Entity
public class Animal {
    public enum Type { FLYING, OTHER }

    /**
     * Validation group.
     */
    public interface Flying {}

    @Id
    @GeneratedValue
    private Integer id;

    private Type type;

    @NotNull(groups = Flying.class)
    private Integer airSpeedVelocity;

    @NotNull
    private Integer weight;

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public Type getType() { return type; }
    public void setType(Type type) { this.type = type; }
    public Integer getAirSpeedVelocity() { return airSpeedVelocity; }
    public void setAirSpeedVelocity(Integer airSpeedVelocity) { this.airSpeedVelocity = airSpeedVelocity; }
    public Integer getWeight() { return weight; }
    public void setWeight(Integer weight) { this.weight = weight;}
}

When saving an Animal with type FLYING , I want to validate that airSpeedVelocity is non-null. When saving any other animal, I don't want this validation.

Currently, I have validations enable to be checked prior to save, so that a 400 Bad Request error is returned if an object is invalid:

    @Bean
    public ValidatingRepositoryEventListener preSaveValidator(
            @Qualifier("defaultValidator") SmartValidator validator,
            ObjectFactory<PersistentEntities> persistentEntitiesFactory) {
        ValidatingRepositoryEventListener eventListener = 
                new ValidatingRepositoryEventListener(persistentEntitiesFactory);
        eventListener.addValidator("beforeCreate", validator);
        eventListener.addValidator("beforeSave", validator);
        return eventListener;
    }
}

Request:

{ "type": "FLYING" }

Current 400 error response:

{
    "errors": [
        {
            "entity": "Animal",
            "property": "weight",
            "invalidValue": null,
            "message": "must not be null"
        }
    ]
}

Desired 400 error response:

{
    "errors": [
        {
            "entity": "Animal",
            "property": "airSpeedVelocity",
            "invalidValue": null,
            "message": "must not be null"
        },
        {
            "entity": "Animal",
            "property": "weight",
            "invalidValue": null,
            "message": "must not be null"
        }
    ]
}

How can I perform this conditional validation, applying the Flying validation group when the request entity is an Animal where type == FLYING ?

One solution is to use a customized Validator that automatically checks the input type, automatically applying the custom validation groups if necessary:

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.SmartValidator;

import javax.validation.groups.Default;

@Configuration
public class RestRepositoryValidatorConfig {
    @Bean
    public ValidatingRepositoryEventListener preSaveValidator(
            AnimalValidationGroupAwareValidator validator,
            ObjectFactory<PersistentEntities> persistentEntitiesFactory) {
        ValidatingRepositoryEventListener eventListener =
                new ValidatingRepositoryEventListener(persistentEntitiesFactory);
        eventListener.addValidator("beforeCreate", validator);
        eventListener.addValidator("beforeSave", validator);
        return eventListener;
    }

    @Component
    public static class AnimalValidationGroupAwareValidator
            implements SmartValidator {
        private final SmartValidator delegate;

        public AnimalValidationGroupAwareValidator(
                @Qualifier("defaultValidator") SmartValidator delegate) {
            this.delegate = delegate;
        }

        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }

        @Override
        public void validate(Object target, Errors errors,
                             Object... validationHints) {
            // If hints are overridden, use those instead
            delegate.validate(target, errors, validationHints);
        }

        @Override
        public void validate(Object target, Errors errors) {
            if (target instanceof Animal animal &&
                Animal.Type.FLYING.equals(animal.getType())) {
                delegate.validate(target, errors,
                        Animal.Flying.class, Default.class);
            } else {
                delegate.validate(target, errors);
            }
        }
    }
}

Note that this also adds the Default validation group, since otherwise the standard validations would not also be performed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM