Provide some value to @Valid annotation and using that value change validation in isValid() method

Viewed 21

I am using two databases in my project and while validating filters provided in the request body, For some request I need validation from db1 and for some I need validation from db2. I have mapped values and its corresponding types in one enum. But how do I determine which mapping I have to use for that particular filter provided in the request body.

Below is my controller demo:

@PostMapping(path = "/v1/abc")
public PageableDto getAll{Something}(
            @RequestAttribute UserClaim userClaim,
            @Valid SearchParametersDto searchParametersDto,
            @RequestBody @Valid List<FilterDto> filters)  

This needs to be validated using DB1.

@PostMapping(path = "/v1/xyz")
    public PageableDto getAll{Something}(
            @RequestAttribute UserClaim userClaim,
            @Valid SearchParametersDto searchParametersDto,
            @RequestBody @Valid List<FilterDto> filters) 

This needs to be validated using DB2.

In the FilterDto class, I have created annotation.

@Getter
@Setter
@FilterValidatorBehaviour
public class FilterDto {
    String name;
    String operation;
    String value;

    @JsonIgnore
    public DataBaseTypeObject getObjectWithFieldName(MappingEnum mapping) {
        //return get map with enum name map
        return mapping.getMappingMap().get(name);
    }
}

In the annotation implementation class. I have overridden isValid method as below.

@Override
    public boolean isValid(FilterDto filterDto, ConstraintValidatorContext ctx) {
       return filterDto.getValue().getClass().equals(
filterDto.getObjectWithFieldName(MappingEnum.DB1_MAPPING).getType()); 
   
 }

Below is MappingEnum class:

@RequiredArgsConstructor
@Getter
public enum MappingEnum {

        DB1_MAPPING(Map.ofEntries(
                Map.entry("field1",new DB1Object("field1", String.class)),
                Map.entry("field2",new DB1Object("field2", String.class)),
                Map.entry("field3",new DB1Object("field3", String.class)),
                )),

        DB2_MAPPING(Map.ofEntries(
                Map.entry("field1",new DB2Object("field1", String.class)),
                Map.entry("field2",new DB2Object("field2", String.class)),
                Map.entry("field3",new DB2Object("field3", String.class)),
                )),


        private final Map<String, ? extends DataBaseTypeObject> mappingMap;

Here DB1Object and DB2Object are subclasses of DataBaseTypeObject.

Any help in the direction of a solution would be great help??

1 Answers

A possible solution is to move the @FilterValidatorBehaviour annotation from FilterDto to the controller itself. The downside is that because you pass a list, you'll have to refactor the validator to accept a List<FilterDto> in stead of just a FilterDto.

The benefit is that you can add custom properties to your @FilterValidatorBehaviour annotation. For example, you could create a property that contains a MappingEnum:

@Documented
@Retention(RUNTIME)
@Target({PARAMETER}) // Make sure to set PARAMETER
@Constraint(validatedBy = FilterValidatorBehaviourValidator.class)
public @interface FilterValidatorBehaviour {
    MappingEnum value(); // Add this
    String message() default "{filterValidatorBehaviour}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

To read this value property, you can implement the initialize() method in your ConstraintValidator and set it as a field:

@Override
public void initialize(FilterValidatorBehaviour constraintAnnotation) {
    this.mappingEnum = constraintAnnotation.value(); // Read mappingEnum from annotation
}

Now you can use this.mappingEnum in your isValid() method. Make sure not to forget that you have to refactor your validator froù ConstraintValidator<FilterValidatorBehaviour, FilterDto> to ConstraintValidator<FilterValidatorBehaviour, List<FilterDto>>.

After that, you can use @FilterValidatorBehaviour(MappingEnum.DB1_MAPPING) or @FilterValidatorBehaviour(MappingEnum.DB2_MAPPING) in your controller. For example:

@PostMapping(path = "/v1/abc")
public PageableDto getAll{Something}(
            @RequestAttribute UserClaim userClaim,
            @Valid SearchParametersDto searchParametersDto,
            // Add the @FilterValidatorBehaviour annotation
            @RequestBody @Valid @FilterValidatorBehaviour(MappingEnum.DB1_MAPPING) List<FilterDto> filters) {
    // TODO: implement
    return null;
}
Related