Pregunta

¿Existe una implementación de (o implementación de terceros para) validación de campo cruzado en Hibernate Validator 4.x? Si no, ¿cuál es la forma más limpia de implementar un validador de campo cruzado?

Como ejemplo, ¿cómo puede usar la API para validar dos propiedades de frijoles son iguales (como validar un campo de contraseña coincide con el campo Verify de contraseña)?

En anotaciones, esperaría algo como:

public class MyBean {
  @Size(min=6, max=50)
  private String pass;

  @Equals(property="pass")
  private String passVerify;
}
¿Fue útil?

Solución

Cada restricción de campo debe manejarse mediante una anotación de validador distinta, o en otras palabras no se sugiere que la práctica tenga la revisión de anotación de validación de un campo contra otros campos; La validación de campo cruzado debe hacerse a nivel de clase. Además, el JSR-303 Sección 2.2 La forma preferida de expresar múltiples validaciones del mismo tipo es a través de una lista de anotaciones. Esto permite especificar el mensaje de error por coincidencia.

Por ejemplo, validando una forma común:

@FieldMatch.List({
        @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
        @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")
})
public class UserRegistrationForm  {
    @NotNull
    @Size(min=8, max=25)
    private String password;

    @NotNull
    @Size(min=8, max=25)
    private String confirmPassword;

    @NotNull
    @Email
    private String email;

    @NotNull
    @Email
    private String confirmEmail;
}

La anotación:

package constraints;

import constraints.impl.FieldMatchValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

/**
 * Validation annotation to validate that 2 fields have the same value.
 * An array of fields and their matching confirmation fields can be supplied.
 *
 * Example, compare 1 pair of fields:
 * @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
 * 
 * Example, compare more than 1 pair of fields:
 * @FieldMatch.List({
 *   @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
 *   @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
 */
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
@Documented
public @interface FieldMatch
{
    String message() default "{constraints.fieldmatch}";

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

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

    /**
     * @return The first field
     */
    String first();

    /**
     * @return The second field
     */
    String second();

    /**
     * Defines several <code>@FieldMatch</code> annotations on the same element
     *
     * @see FieldMatch
     */
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
            @interface List
    {
        FieldMatch[] value();
    }
}

El validador:

package constraints.impl;

import constraints.FieldMatch;
import org.apache.commons.beanutils.BeanUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object>
{
    private String firstFieldName;
    private String secondFieldName;

    @Override
    public void initialize(final FieldMatch constraintAnnotation)
    {
        firstFieldName = constraintAnnotation.first();
        secondFieldName = constraintAnnotation.second();
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context)
    {
        try
        {
            final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
            final Object secondObj = BeanUtils.getProperty(value, secondFieldName);

            return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
        }
        catch (final Exception ignore)
        {
            // ignore
        }
        return true;
    }
}

Otros consejos

Te sugiero otra posible solución. ¡Quizás menos elegante, pero más fácil!

public class MyBean {
  @Size(min=6, max=50)
  private String pass;

  private String passVerify;

  @AssertTrue(message="passVerify field should be equal than pass field")
  private boolean isValid() {
    return this.pass.equals(this.passVerify);
  }
}

los isValid El método es invocado por el validador automáticamente.

Me sorprende que esto no esté disponible fuera de la caja. De todos modos, aquí hay una posible solución.

He creado un validador de nivel de clase, no el nivel de campo como se describe en la pregunta original.

Aquí está el código de anotación:

package com.moa.podium.util.constraints;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = MatchesValidator.class)
@Documented
public @interface Matches {

  String message() default "{com.moa.podium.util.constraints.matches}";

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

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

  String field();

  String verifyField();
}

Y el validador en sí:

package com.moa.podium.util.constraints;

import org.mvel2.MVEL;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MatchesValidator implements ConstraintValidator<Matches, Object> {

  private String field;
  private String verifyField;


  public void initialize(Matches constraintAnnotation) {
    this.field = constraintAnnotation.field();
    this.verifyField = constraintAnnotation.verifyField();
  }

  public boolean isValid(Object value, ConstraintValidatorContext context) {
    Object fieldObj = MVEL.getProperty(field, value);
    Object verifyFieldObj = MVEL.getProperty(verifyField, value);

    boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);

    if (neitherSet) {
      return true;
    }

    boolean matches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);

    if (!matches) {
      context.disableDefaultConstraintViolation();
      context.buildConstraintViolationWithTemplate("message")
          .addNode(verifyField)
          .addConstraintViolation();
    }

    return matches;
  }
}

Tenga en cuenta que he usado MVEL para inspeccionar las propiedades del objeto que se valida. Esto podría reemplazarse con las API estándar de reflexión o si es una clase específica que está validando, los métodos de accesorios mismos.

La anotación @matches se puede usar en un frijol de la siguiente manera:

@Matches(field="pass", verifyField="passRepeat")
public class AccountCreateForm {

  @Size(min=6, max=50)
  private String pass;
  private String passRepeat;

  ...
}

Como descargo de responsabilidad, escribí esto en los últimos 5 minutos, por lo que probablemente aún no he solucionado todos los insectos. Actualizaré la respuesta si algo sale mal.

Con Hibernate Validator 4.1.0.Final recomiendo usar @Scriptassert. Exceprt de su Javadoc:

Las expresiones de script se pueden escribir en cualquier lenguaje de secuencia de comandos o expresión, para el cual un JSR 223 ("Scripting para la plataforma Javatm") se puede encontrar un motor compatible en el classpath.

Nota: La evaluación está siendo realizada por una secuencia de comandos "motor"Ejecutando en Java VM, por lo tanto en Java" Servidor del servidor ", no en "Lado del cliente" como se indica en algunos comentarios.

Ejemplo:

@ScriptAssert(lang = "javascript", script = "_this.passVerify.equals(_this.pass)")
public class MyBean {
  @Size(min=6, max=50)
  private String pass;

  private String passVerify;
}

o con alias más cortos y nulo:

@ScriptAssert(lang = "javascript", alias = "_",
    script = "_.passVerify != null && _.passVerify.equals(_.pass)")
public class MyBean {
  @Size(min=6, max=50)
  private String pass;

  private String passVerify;
}

o con Java 7+ NULL SAFE Objects.equals():

@ScriptAssert(lang = "javascript", script = "Objects.equals(_this.passVerify, _this.pass)")
public class MyBean {
  @Size(min=6, max=50)
  private String pass;

  private String passVerify;
}

Sin embargo, no hay nada de malo en un validador de nivel de clase personalizado @Partidos solución.

Las validaciones de los campos cruzados se pueden hacer creando restricciones personalizadas.

Ejemplo:- Compare la contraseña y confirmar los campos de Wordword de la instancia del usuario.

Comparaciones

@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy=CompareStringsValidator.class)
@Documented
public @interface CompareStrings {
    String[] propertyNames();
    StringComparisonMode matchMode() default EQUAL;
    boolean allowNull() default false;
    String message() default "";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

StringComparisonMode

public enum StringComparisonMode {
    EQUAL, EQUAL_IGNORE_CASE, NOT_EQUAL, NOT_EQUAL_IGNORE_CASE
}

Comparestringsvalidator

public class CompareStringsValidator implements ConstraintValidator<CompareStrings, Object> {

    private String[] propertyNames;
    private StringComparisonMode comparisonMode;
    private boolean allowNull;

    @Override
    public void initialize(CompareStrings constraintAnnotation) {
        this.propertyNames = constraintAnnotation.propertyNames();
        this.comparisonMode = constraintAnnotation.matchMode();
        this.allowNull = constraintAnnotation.allowNull();
    }

    @Override
    public boolean isValid(Object target, ConstraintValidatorContext context) {
        boolean isValid = true;
        List<String> propertyValues = new ArrayList<String> (propertyNames.length);
        for(int i=0; i<propertyNames.length; i++) {
            String propertyValue = ConstraintValidatorHelper.getPropertyValue(String.class, propertyNames[i], target);
            if(propertyValue == null) {
                if(!allowNull) {
                    isValid = false;
                    break;
                }
            } else {
                propertyValues.add(propertyValue);
            }
        }

        if(isValid) {
            isValid = ConstraintValidatorHelper.isValid(propertyValues, comparisonMode);
        }

        if (!isValid) {
          /*
           * if custom message was provided, don't touch it, otherwise build the
           * default message
           */
          String message = context.getDefaultConstraintMessageTemplate();
          message = (message.isEmpty()) ?  ConstraintValidatorHelper.resolveMessage(propertyNames, comparisonMode) : message;

          context.disableDefaultConstraintViolation();
          ConstraintViolationBuilder violationBuilder = context.buildConstraintViolationWithTemplate(message);
          for (String propertyName : propertyNames) {
            NodeBuilderDefinedContext nbdc = violationBuilder.addNode(propertyName);
            nbdc.addConstraintViolation();
          }
        }    

        return isValid;
    }
}

RestrictvalidatorHelper

public abstract class ConstraintValidatorHelper {

public static <T> T getPropertyValue(Class<T> requiredType, String propertyName, Object instance) {
        if(requiredType == null) {
            throw new IllegalArgumentException("Invalid argument. requiredType must NOT be null!");
        }
        if(propertyName == null) {
            throw new IllegalArgumentException("Invalid argument. PropertyName must NOT be null!");
        }
        if(instance == null) {
            throw new IllegalArgumentException("Invalid argument. Object instance must NOT be null!");
        }
        T returnValue = null;
        try {
            PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, instance.getClass());
            Method readMethod = descriptor.getReadMethod();
            if(readMethod == null) {
                throw new IllegalStateException("Property '" + propertyName + "' of " + instance.getClass().getName() + " is NOT readable!");
            }
            if(requiredType.isAssignableFrom(readMethod.getReturnType())) {
                try {
                    Object propertyValue = readMethod.invoke(instance);
                    returnValue = requiredType.cast(propertyValue);
                } catch (Exception e) {
                    e.printStackTrace(); // unable to invoke readMethod
                }
            }
        } catch (IntrospectionException e) {
            throw new IllegalArgumentException("Property '" + propertyName + "' is NOT defined in " + instance.getClass().getName() + "!", e);
        }
        return returnValue; 
    }

    public static boolean isValid(Collection<String> propertyValues, StringComparisonMode comparisonMode) {
        boolean ignoreCase = false;
        switch (comparisonMode) {
        case EQUAL_IGNORE_CASE:
        case NOT_EQUAL_IGNORE_CASE:
            ignoreCase = true;
        }

        List<String> values = new ArrayList<String> (propertyValues.size());
        for(String propertyValue : propertyValues) {
            if(ignoreCase) {
                values.add(propertyValue.toLowerCase());
            } else {
                values.add(propertyValue);
            }
        }

        switch (comparisonMode) {
        case EQUAL:
        case EQUAL_IGNORE_CASE:
            Set<String> uniqueValues = new HashSet<String> (values);
            return uniqueValues.size() == 1 ? true : false;
        case NOT_EQUAL:
        case NOT_EQUAL_IGNORE_CASE:
            Set<String> allValues = new HashSet<String> (values);
            return allValues.size() == values.size() ? true : false;
        }

        return true;
    }

    public static String resolveMessage(String[] propertyNames, StringComparisonMode comparisonMode) {
        StringBuffer buffer = concatPropertyNames(propertyNames);
        buffer.append(" must");
        switch(comparisonMode) {
        case EQUAL:
        case EQUAL_IGNORE_CASE:
            buffer.append(" be equal");
            break;
        case NOT_EQUAL:
        case NOT_EQUAL_IGNORE_CASE:
            buffer.append(" not be equal");
            break;
        }
        buffer.append('.');
        return buffer.toString();
    }

    private static StringBuffer concatPropertyNames(String[] propertyNames) {
        //TODO improve concating algorithm
        StringBuffer buffer = new StringBuffer();
        buffer.append('[');
        for(String propertyName : propertyNames) {
            char firstChar = Character.toUpperCase(propertyName.charAt(0));
            buffer.append(firstChar);
            buffer.append(propertyName.substring(1));
            buffer.append(", ");
        }
        buffer.delete(buffer.length()-2, buffer.length());
        buffer.append("]");
        return buffer;
    }
}

Usuario

@CompareStrings(propertyNames={"password", "confirmPassword"})
public class User {
    private String password;
    private String confirmPassword;

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    public String getConfirmPassword() { return confirmPassword; }
    public void setConfirmPassword(String confirmPassword) { this.confirmPassword =  confirmPassword; }
}

Prueba

    public void test() {
        User user = new User();
        user.setPassword("password");
        user.setConfirmPassword("paSSword");
        Set<ConstraintViolation<User>> violations = beanValidator.validate(user);
        for(ConstraintViolation<User> violation : violations) {
            logger.debug("Message:- " + violation.getMessage());
        }
        Assert.assertEquals(violations.size(), 1);
    }

Producción Message:- [Password, ConfirmPassword] must be equal.

Al usar la restricción de validación de comparaciones, también podemos comparar más de dos propiedades y podemos mezclar cualquiera de los cuatro métodos de comparación de cadenas.

ColorCoice

@CompareStrings(propertyNames={"color1", "color2", "color3"}, matchMode=StringComparisonMode.NOT_EQUAL, message="Please choose three different colors.")
public class ColorChoice {

    private String color1;
    private String color2;
    private String color3;
        ......
}

Prueba

ColorChoice colorChoice = new ColorChoice();
        colorChoice.setColor1("black");
        colorChoice.setColor2("white");
        colorChoice.setColor3("white");
        Set<ConstraintViolation<ColorChoice>> colorChoiceviolations = beanValidator.validate(colorChoice);
        for(ConstraintViolation<ColorChoice> violation : colorChoiceviolations) {
            logger.debug("Message:- " + violation.getMessage());
        }

Producción Message:- Please choose three different colors.

Del mismo modo, podemos tener comparación de comparación, comparaciones, etc., etc. Restricciones de validación de campos cruzados.

PD No he probado este código en el entorno de producción (aunque lo probé en el entorno de Dev), así que considere este código como la liberación de hitos. Si encuentra un error, escriba un buen comentario. :)

He probado el ejemplo de Alberthoven (Hibernate-Validator 4.0.2.Ga) y obtengo una ValidationException: „Los métodos anotados deben seguir la Convención de nombres de Javabeans. Match () no lo hace. "TAMBIÉN. Después de cambiar el nombre del método de "Match" a "IsValid", funciona.

public class Password {

    private String password;

    private String retypedPassword;

    public Password(String password, String retypedPassword) {
        super();
        this.password = password;
        this.retypedPassword = retypedPassword;
    }

    @AssertTrue(message="password should match retyped password")
    private boolean isValid(){
        if (password == null) {
            return retypedPassword == null;
        } else {
            return password.equals(retypedPassword);
        }
    }

    public String getPassword() {
        return password;
    }

    public String getRetypedPassword() {
        return retypedPassword;
    }

}

Si está utilizando el marco Spring, puede usar el lenguaje de expresión de Spring (SPEL) para eso. He escrito una pequeña biblioteca que proporciona un validador JSR-303 basado en Spel, ¡hace que las validaciones de campo cruzado sea muy fácil! Echa un vistazo a https://github.com/jirutka/validator-spring.

Esto validará la longitud y la igualdad de los campos de contraseña.

@SpELAssert(value = "pass.equals(passVerify)",
            message = "{validator.passwords_not_same}")
public class MyBean {

    @Size(min = 6, max = 50)
    private String pass;

    private String passVerify;
}

También puede modificar esto fácilmente para validar los campos de contraseña solo cuando no están vacíos.

@SpELAssert(value = "pass.equals(passVerify)",
            applyIf = "pass || passVerify",
            message = "{validator.passwords_not_same}")
public class MyBean {

    @Size(min = 6, max = 50)
    private String pass;

    private String passVerify;
}

No tengo la reputación de comentar sobre la primera respuesta, pero quería agregar que he agregado pruebas unitarias para la respuesta ganadora y tengo las siguientes observaciones:

  • Si se equivoca los nombres primero o de campo, obtiene un error de validación como si los valores no coincidan. No te tropiezes con los errores ortográficos, por ejemplo,

@FieldMatch (primero = "inválidoFieldName1 ", segundo =" WidalFieldName2 ")

  • El validador voluntad Acepte tipos de datos equivalentes, es decir, todos estos pasarán con FieldMatch:

String private Stringfield = "1";

Integer Integerfield privado = nuevo entero (1)

Private int intfield = 1;

  • Si los campos son de un tipo de objeto que no implementa igual, la validación fallará.

Me gusta la idea de Jakub Jirutka para usar el lenguaje de expresión de primavera. Si no desea agregar otra biblioteca/dependencia (suponiendo que ya use Spring), aquí hay una implementación simplificada de su idea.

La restricción:

@Constraint(validatedBy=ExpressionAssertValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpressionAssert {
    String message() default "expression must evaluate to true";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String value();
}

El validador:

public class ExpressionAssertValidator implements ConstraintValidator<ExpressionAssert, Object> {
    private Expression exp;

    public void initialize(ExpressionAssert annotation) {
        ExpressionParser parser = new SpelExpressionParser();
        exp = parser.parseExpression(annotation.value());
    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {
        return exp.getValue(value, Boolean.class);
    }
}

Aplicar así:

@ExpressionAssert(value="pass == passVerify", message="passwords must be same")
public class MyBean {
    @Size(min=6, max=50)
    private String pass;
    private String passVerify;
}

Muy buena solución Bradhouse. ¿Hay alguna forma de aplicar la anotación @matches a más de un campo?

EDITAR: Aquí está la solución que se me ocurrió para responder a esta pregunta, modifiqué la restricción para aceptar una matriz en lugar de un solo valor:

@Matches(fields={"password", "email"}, verifyFields={"confirmPassword", "confirmEmail"})
public class UserRegistrationForm  {

    @NotNull
    @Size(min=8, max=25)
    private String password;

    @NotNull
    @Size(min=8, max=25)
    private String confirmPassword;


    @NotNull
    @Email
    private String email;

    @NotNull
    @Email
    private String confirmEmail;
}

El código para la anotación:

package springapp.util.constraints;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = MatchesValidator.class)
@Documented
public @interface Matches {

  String message() default "{springapp.util.constraints.matches}";

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

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

  String[] fields();

  String[] verifyFields();
}

Y la implementación:

package springapp.util.constraints;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.apache.commons.beanutils.BeanUtils;

public class MatchesValidator implements ConstraintValidator<Matches, Object> {

    private String[] fields;
    private String[] verifyFields;

    public void initialize(Matches constraintAnnotation) {
        fields = constraintAnnotation.fields();
        verifyFields = constraintAnnotation.verifyFields();
    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {

        boolean matches = true;

        for (int i=0; i<fields.length; i++) {
            Object fieldObj, verifyFieldObj;
            try {
                fieldObj = BeanUtils.getProperty(value, fields[i]);
                verifyFieldObj = BeanUtils.getProperty(value, verifyFields[i]);
            } catch (Exception e) {
                //ignore
                continue;
            }
            boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
            if (neitherSet) {
                continue;
            }

            boolean tempMatches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);

            if (!tempMatches) {
                addConstraintViolation(context, fields[i]+ " fields do not match", verifyFields[i]);
            }

            matches = matches?tempMatches:matches;
        }
        return matches;
    }

    private void addConstraintViolation(ConstraintValidatorContext context, String message, String field) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(message).addNode(field).addConstraintViolation();
    }
}

Debe llamarlo explícitamente. En el ejemplo anterior, Bradhouse le ha dado todos los pasos para escribir una restricción personalizada.

Agregue este código en su clase de llamadas.

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();

Set<ConstraintViolation<yourObjectClass>> constraintViolations = validator.validate(yourObject);

En el caso anterior sería

Set<ConstraintViolation<AccountCreateForm>> constraintViolations = validator.validate(objAccountCreateForm);

¿Por qué no probar ovalado? http://oval.sourceforge.net/

Parece que admite OGNL, así que tal vez podrías hacerlo por un

@Assert(expr = "_value ==_this.pass").

Ustedes son increíbles. Ideas realmente sorprendentes. me gusta Alberthoven's y McGin's La mayoría, así que decidí combinar ambas ideas. Y desarrolle una solución genérica para atender todos los casos. Aquí está mi solución propuesta.

@Documented
@Constraint(validatedBy = NotFalseValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotFalse {


    String message() default "NotFalse";
    String[] messages();
    String[] properties();
    String[] verifiers();

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

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

}

public class NotFalseValidator implements ConstraintValidator<NotFalse, Object> {
    private String[] properties;
    private String[] messages;
    private String[] verifiers;
    @Override
    public void initialize(NotFalse flag) {
        properties = flag.properties();
        messages = flag.messages();
        verifiers = flag.verifiers();
    }

    @Override
    public boolean isValid(Object bean, ConstraintValidatorContext cxt) {
        if(bean == null) {
            return true;
        }

        boolean valid = true;
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);

        for(int i = 0; i< properties.length; i++) {
           Boolean verified = (Boolean) beanWrapper.getPropertyValue(verifiers[i]);
           valid &= isValidProperty(verified,messages[i],properties[i],cxt);
        }

        return valid;
    }

    boolean isValidProperty(Boolean flag,String message, String property, ConstraintValidatorContext cxt) {
        if(flag == null || flag) {
            return true;
        } else {
            cxt.disableDefaultConstraintViolation();
            cxt.buildConstraintViolationWithTemplate(message)
                    .addPropertyNode(property)
                    .addConstraintViolation();
            return false;
        }

    }



}

@NotFalse(
        messages = {"End Date Before Start Date" , "Start Date Before End Date" } ,
        properties={"endDateTime" , "startDateTime"},
        verifiers = {"validDateRange" , "validDateRange"})
public class SyncSessionDTO implements ControllableNode {
    @NotEmpty @NotPastDate
    private Date startDateTime;

    @NotEmpty
    private Date endDateTime;



    public Date getStartDateTime() {
        return startDateTime;
    }

    public void setStartDateTime(Date startDateTime) {
        this.startDateTime = startDateTime;
    }

    public Date getEndDateTime() {
        return endDateTime;
    }

    public void setEndDateTime(Date endDateTime) {
        this.endDateTime = endDateTime;
    }


    public Boolean getValidDateRange(){
        if(startDateTime != null && endDateTime != null) {
            return startDateTime.getTime() <= endDateTime.getTime();
        }

        return null;
    }

}

Solución realizada con pregunta:Cómo acceder a un campo que se describe en la propiedad de anotación

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Match {

    String field();

    String message() default "";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MatchValidator.class)
@Documented
public @interface EnableMatchConstraint {

    String message() default "Fields must match!";

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

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

public class MatchValidator implements  ConstraintValidator<EnableMatchConstraint, Object> {

    @Override
    public void initialize(final EnableMatchConstraint constraint) {}

    @Override
    public boolean isValid(final Object o, final ConstraintValidatorContext context) {
        boolean result = true;
        try {
            String mainField, secondField, message;
            Object firstObj, secondObj;

            final Class<?> clazz = o.getClass();
            final Field[] fields = clazz.getDeclaredFields();

            for (Field field : fields) {
                if (field.isAnnotationPresent(Match.class)) {
                    mainField = field.getName();
                    secondField = field.getAnnotation(Match.class).field();
                    message = field.getAnnotation(Match.class).message();

                    if (message == null || "".equals(message))
                        message = "Fields " + mainField + " and " + secondField + " must match!";

                    firstObj = BeanUtils.getProperty(o, mainField);
                    secondObj = BeanUtils.getProperty(o, secondField);

                    result = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
                    if (!result) {
                        context.disableDefaultConstraintViolation();
                        context.buildConstraintViolationWithTemplate(message).addPropertyNode(mainField).addConstraintViolation();
                        break;
                    }
                }
            }
        } catch (final Exception e) {
            // ignore
            //e.printStackTrace();
        }
        return result;
    }
}

Y cómo usarlo...? Como esto:

@Entity
@EnableMatchConstraint
public class User {

    @NotBlank
    private String password;

    @Match(field = "password")
    private String passwordConfirmation;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top