Categories
Java

class based JSR303 – validator adding error on property

Sometimes it is necessary to validate one property or multiple properties of a class at one time or in relation to each other, but you want to add a constraint violation to the specific property. The solution will described in this post

You have your model:

@ValidateFieldIfFlag
public class Model {
@Id
@GeneratedValue
private int id;
private boolean flag;
private String field;
... // getter and setter following
}
@ValidateFieldIfFlag public class Model { @Id @GeneratedValue private int id; private boolean flag; private String field; ... // getter and setter following }
@ValidateFieldIfFlag
public class Model {
    @Id
    @GeneratedValue
    private int id;

    private boolean flag;

    private String field;

    ... // getter and setter following
}

As you see the class uses a custom validator ValidateFieldIfFlag to validate a Model instance.

So you have the annotation:

@Constraint(validatedBy = {ValidateFieldIfFlagValidator.class})
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateFieldIfFlagValidator
{
String message() default "my error message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint(validatedBy = {ValidateFieldIfFlagValidator.class}) @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) @Documented public @interface ValidateFieldIfFlagValidator { String message() default "my error message"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
@Constraint(validatedBy = {ValidateFieldIfFlagValidator.class})
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateFieldIfFlagValidator
{
	String message() default "my error message";

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};
}

And your validator:

public class ValidateFieldIfFlagValidator implements ConstraintValidator<ValidateFieldIfFlagValidator, Model>
{
public boolean isValid(Model model, ConstraintValidatorContext context) {
if (!model.isFlag()) return true;
//if flag is set, field should be nonEmpty
return model.getField() != null && !model.getField().isEmpty();
}
}
public class ValidateFieldIfFlagValidator implements ConstraintValidator<ValidateFieldIfFlagValidator, Model> { public boolean isValid(Model model, ConstraintValidatorContext context) { if (!model.isFlag()) return true; //if flag is set, field should be nonEmpty return model.getField() != null && !model.getField().isEmpty(); } }
public class ValidateFieldIfFlagValidator implements ConstraintValidator<ValidateFieldIfFlagValidator, Model>
{
    public boolean isValid(Model model, ConstraintValidatorContext context) {
        if (!model.isFlag()) return true;
        //if flag is set, field should be nonEmpty      
        return model.getField() != null && !model.getField().isEmpty();
    }
}

The validator will validate the field to be non empty, if the flag is set. Unfortunately you will receive a ConstraintViolationException for the whole instance and not only for the field. To change this, you have to modify the validator:

public class ValidateFieldIfFlagValidator implements ConstraintValidator<ValidateFieldIfFlagValidator, Model>
{
public boolean isValid(Model model, ConstraintValidatorContext context) {
if (!model.isFlag()) return true;
//if flag is set, field should be nonEmpty
if (model.getField() != null && !model.getField().isEmpty()) return true;
ConstraintValidatorContext.ConstraintViolationBuilder constraintViolationBuilder = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
constraintViolationBuilder.addPropertyNode("field").addConstraintViolation();
context.disableDefaultConstraintViolation();
return false;
}
}
public class ValidateFieldIfFlagValidator implements ConstraintValidator<ValidateFieldIfFlagValidator, Model> { public boolean isValid(Model model, ConstraintValidatorContext context) { if (!model.isFlag()) return true; //if flag is set, field should be nonEmpty if (model.getField() != null && !model.getField().isEmpty()) return true; ConstraintValidatorContext.ConstraintViolationBuilder constraintViolationBuilder = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()); constraintViolationBuilder.addPropertyNode("field").addConstraintViolation(); context.disableDefaultConstraintViolation(); return false; } }
public class ValidateFieldIfFlagValidator implements ConstraintValidator<ValidateFieldIfFlagValidator, Model>
{
    public boolean isValid(Model model, ConstraintValidatorContext context) {
        if (!model.isFlag()) return true;
        //if flag is set, field should be nonEmpty      
        if (model.getField() != null && !model.getField().isEmpty()) return true;
        ConstraintValidatorContext.ConstraintViolationBuilder constraintViolationBuilder = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
        constraintViolationBuilder.addPropertyNode("field").addConstraintViolation();
        context.disableDefaultConstraintViolation();        
        return false;
    }
}

This suppress the default, class matched ConstraintViolationException, and creates a ConstraintViolationException matching the property “field”.