Defining the Custom Constraint Annotation
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Constraint(validatedBy = RecordIdValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RecordExists {
String message() default "The provided identifier does not exist";
boolean mandatory() default false;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Base Validator with Service Injection
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.Annotation;
import java.util.Objects;
import java.util.function.Predicate;
@Slf4j
public abstract class SpringAwareValidator<A extends Annotation, T>
implements ConstraintValidator<A, T> {
protected boolean mandatoryCheck;
protected Predicate<T> validationRule = input -> true;
@Autowired
protected PersistenceService persistenceService;
@Override
public boolean isValid(T value, ConstraintValidatorContext context) {
if (!mandatoryCheck && Objects.isNull(value)) {
return true;
}
return validationRule.test(value);
}
}
Concrtee Implementation for Identifier Lookup
public class RecordIdValidator extends SpringAwareValidator<RecordExists, String> {
@Override
public void initialize(RecordExists annotation) {
validationRule = id -> {
long matches = persistenceService.count(
new QueryWrapper<DataRecord>()
.eq("recordId", id)
);
return matches > 0;
};
this.mandatoryCheck = annotation.mandatory();
super.initialize(annotation);
}
}
Applying the Annotation and Group Sequences
Inside a transefr object:
public class UpdateRequest {
@ApiModelProperty("Record identifier")
@NotEmpty(message = "Identifier must not be blank")
@RecordExists(groups = ValidationLevel.Critical.class)
@Length(max = 32, message = "Identifier length cannot exceed 32 characters")
private String recordId;
public interface Critical {}
public interface Extended {}
@GroupSequence({Default.class, Critical.class, Extended.class})
public interface OrderedChecks {}
}
Tirggering Validation in a REST Controller
@PostMapping
public ResponseEntity<?> update(@RequestBody @Validated(UpdateRequest.OrderedChecks.class) UpdateRequest payload) {
// process valid request
}