Home > Software engineering >  Pass custom parameter to ConstraintValidator
Pass custom parameter to ConstraintValidator

Time:05-15

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:

  1. 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.

  • Related