I'm working on this springboot application where I need to do some validations on values passed from http call and I'm using class level validation as explained here.
I'm using somethink like this:
@ValidRequest
public class EventRequest {
String date;
}
Response create(@Valid EventRequest request) {
..
}
Response update(Long entityId, @Valid EventRequest request) {
...
}
public class ValidRequestValidator
implements ConstraintValidator<ValidRequest, EventRequest> {
In the class ValidRequestValidator, where I implement the ConstraintValidator interface, I need to check if there is another Event entity in the database that meet some conditions on field date. When I want to create a new entity is simple, I perform a query, but when I need to update I need to exclude the entity I'm currently trying to update.
Is there a way to pass entityId parameter to @ValidRequest custom validator? I know a way is to add the field entityId to the class EventRequest, but I would like to maintain this separation because entityId is coming from a query parameter.
Thank for your help!
CodePudding user response:
Additional to the field-specific(Single Parameter Constraint
) you can implement constraint for the whole method(Cross-Parameter Constraint
). This will provide ability to pass all parameters of certain method to validator.
Annotation definition:
Annotation used two validators and can be applied to the Method or Type.
@Constraint(validatedBy = {ValidRequestMethodValidator.class, ValidRequestTypeValidator.class})
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidRequest {
String message() default "Request is invalid!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
}
Constraint Validator which will handle single parameter:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ValidRequestTypeValidator implements ConstraintValidator<ValidRequest, EventRequest> {
@Override
public boolean isValid(EventRequest request, ConstraintValidatorContext constraintValidatorContext) {
// logic here
return false;
}
}
Constraint Validator which will handle all parameters of specific method:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ValidRequestMethodValidator implements ConstraintValidator<ValidRequest, Object[]> {
@Override
public boolean isValid(Object[] objects, ConstraintValidatorContext constraintValidatorContext) {
Long entityId = null;
EventRequest eventRequest = null;
if (objects[0] instanceof Long) {
entityId = (Long) objects[0];
}
if (objects[0] instanceof EventRequest) {
eventRequest = (EventRequest) objects[0];
}
if (objects[1] instanceof EventRequest) {
eventRequest = (EventRequest) objects[1];
}
//logic here
return false;
}
}
Please note, we have to annotate the beans, which shall be validated, with @org.springframework.validation.annotation.Validated
annotation to get method validators to work automatically.
Example of usage:
- Mixed usage,
@ValidRequest
annotation defined on method and single parameter level.
@ValidRequest
public class EventRequest {
public String value;
}
@RestController
@Validated
public class Controller {
Response create(@Valid EventRequest request) {
return new Response();
}
@ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response update(Long entityId, EventRequest request) {
return new Response();
}
}
For create
method ValidRequestTypeValidator
will be executed.
For update
method ValidRequestMethodValidator
will be executed.
2. Define annotation only for methods
@RestController
@Validated
public class Controller {
@ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response create(EventRequest request) {
return new Response();
}
@ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response update(Long entityId, EventRequest request) {
return new Response();
}
}
For create
method ValidRequestMethodValidator
will be executed with one element objects array
For update
method ValidRequestMethodValidator
will be executed with two elements objects array
3. Define annotation for a single parameter and method at the same time
@ValidRequest
public class EventRequest {
public String value;
}
@RestController
@Validated
public class Controller {
@ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response update(Long entityId, @Valid EventRequest request) {
return new Response();
}
}
First will be executed single parameter validator ValidRequestTypeValidator
.
If it will passed validation then second method validator ValidRequestMethodValidator
will be executed.
Probably only one method-level validation will be sufficient to handle your issue. I described all variants, just for information maybe will be useful.