As I commented earlier and following Nicko's answer from here, you can achieve what you want with the following code:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
public @interface FieldMatch {
String message() default "something is wrong!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* @return The first field
*/
String first();
/**
* @return The second field
*/
String second();
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
FieldMatch[] value();
}
public static class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
@Override
public void initialize(FieldMatch fieldMatch) {
firstFieldName = fieldMatch.first();
secondFieldName = fieldMatch.second();
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
try {
final Object firstObj = getProperty(object, firstFieldName);
final Object secondObj = getProperty(object, secondFieldName);
if(firstObj == null && secondObj == null || firstObj != null && secondObj != null) {
return false;
}
} catch (final Exception ignore) {
// ignore
}
return true;
}
private Object getProperty(Object value, String fieldName) {
Field[] fields = value.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals(fieldName)) {
field.setAccessible(true);
try {
return field.get(value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
}
}
Usage :
@FieldMatch.List({
@FieldMatch(first = "name", second = "people"),
@FieldMatch(first = "age", second = "abc")
})
public class Foo {
private String name;
private List<String> people;
private int age;
private Boolean abc;
}
The only difference for you is that you don't want to check if the contents are equal, just if one field is null and the other isn't.
EDIT:
To get the object on your ExceptionHandler as asked via comments, you simply wrap the exception around a custom one and pass the object when you throw it, i.e.:
public class CustomException extends Exception {
private String message;
private Object model;
public CustomException(String message, Object model) {
super(message);
this.model = model;
}
public Object getModel() {
return model;
}
}
With this, you can simply get it like this:
@ExceptionHandler(CustomException.class)
public ModelAndView handleCustomException(CustomException ex) {
Object obj = ex.getModel();
//do whatever you have to
}