Saltar al contenido

Validación de campo cruzado con Hibernate Validator (JSR 303)

El paso a paso o código que verás en este artículo es la resolución más sencilla y válida que hallamos a tu duda o problema.

Solución:

Cada restricción de campo debe ser manejada por una anotación de validación distinta, o en otras palabras, no es una práctica recomendada tener la anotación de validación de un campo que se compara con otros campos; La validación de campo cruzado debe realizarse a nivel de clase. Además, la forma preferida de JSR-303 Sección 2.2 para 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 un formulario 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[] payload() default ;

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

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

    /**
     * Defines several @FieldMatch 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

    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
         firstObj != null && firstObj.equals(secondObj);
        
        catch (final Exception ignore)
        
            // ignore
        
        return true;
    

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 validador invoca el método automáticamente.

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

Creé 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[] payload() default ;

  String field();

  String verifyField();

Y el propio validador:

package com.moa.podium.util.constraints;

import org.mvel2.MVEL;

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

public class MatchesValidator implements ConstraintValidator 

  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 de reflexión estándar o, si está validando una clase específica, los métodos de acceso en sí.

La anotación @Matches se puede usar en un bean 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 haya solucionado todos los errores. Actualizaré la respuesta si algo sale mal.

Finalizando este artículo puedes encontrar las reseñas de otros administradores, tú asimismo eres capaz dejar el tuyo si lo crees conveniente.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *