This is a bit like Resh32
's answer but this will also bind the validation message with a specific field of the validated object.
The validation annotation class will be like the following one.
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author rumman
* @since 9/23/19
*/
@Target(TYPE)
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = NotNullAnyValidator.class)
public @interface NotNullAny {
String[] fieldNames();
String errorOnProperty();
String messageKey() default "{error.required}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator class will be like the following.
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Objects;
import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
/**
* @author rumman
* @since 9/23/19
*/
public class NotNullAnyValidator implements ConstraintValidator<NotNullAny, Object> {
private String[] fieldNames;
private String errorOnProperty;
private String messageKey;
@Override
public void initialize(NotNullAny validateDateRange) {
fieldNames = validateDateRange.fieldNames();
errorOnProperty = validateDateRange.errorOnProperty();
messageKey = validateDateRange.messageKey();
}
@Override
public boolean isValid(Object obj, ConstraintValidatorContext validatorContext) {
Object[] fieldValues = new Object[fieldNames.length];
try {
for (int i = 0; i < fieldValues.length; i++) {
fieldValues[i] = getPropertyDescriptor(obj.getClass(), fieldNames[i]).getReadMethod().invoke(obj);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
if (Arrays.stream(fieldValues).noneMatch(Objects::nonNull)) {
validatorContext.buildConstraintViolationWithTemplate(messageKey)
.addPropertyNode(errorOnProperty)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
return true;
}
}
Pay attention to the last if
condition block, this checks if no non null
value is found then specifies the error message, the property with which the error message will be bound to and will add the constraint violation.
To use the annotation in a class
/**
* @author rumman
* @since 9/23/19
*/
@NotNullAny(fieldNames = {"field1", "field2", "field3"},
errorOnProperty = "field1",
messageKey = "my.error.msg.key")
public class TestEntityForValidation {
private String field1;
private String field2;
private String field3;
// standard constructor(s) and getter & setters below
}