'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?
Solution 1:[1]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | M. Justin |
