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.