Criando validações de Bean Validation customizadas

Bean Validation é uma especificação Java para tratar validações de dados de forma centralizada, pois as validações são inseridas no próprio modelo através de anotações, assim possibilitando a consistência das informações em diferentes camadas além de possuir integrações com outras especificações como JSF, JPA, Hibernate e JAX-RS.

O Bean Validation já fornece uma série de constraints como: @NotNull, @Size, @Min, @Max, entre outros, no entanto, nem sempre essas anotações padrões resolvem nosso problemas do dia a dia, com isso vou demonstrar um exemplo de como criar uma validação customizada para garantir que um atributo seja apto para ser convertido para um tipo BigDecimal.

O primeiro passo é criar uma anotação para marcar o atributo para validação e configurar algumas propriedades.

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = StringAsBigDecimalValidator.class)
public @interface StringAsBigDecimalValid {

    String message() default "Valor inválido";

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

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

    String value() default "";
}

As anotações dão as seguintes características:

  • @Target({FIELD}): A anotação apenas pode ser utilizada em fields, ou seja, em variáveis globais da classe;
  • @Retention(RUNTIME): Essa validação é apenas em tempo de execução;
  • @Constraint(validatedBy = StringAsBigDecimalValidator.class): Define que a classe StringAsBigDecimalValidator será responsável pela implementação da validação.

Os atributos message, groups, payload e value são os padrões das anotações de Bean Validation, onde tem os seguintes objetivos:

  • message: A mensagem emitida em caso de erro na validação;
  • groups: Para utilização de grupo de validações de Bean Validation;
  • payloads: Para configurar o grau do erro de validação;
  • value: É a propriedade que recebe o valor do atributo inserido.

Após criado a anotação para invocar a validação, é preciso implementar regra de validação, que é feita em uma classe concreta StringAsBigDecimalValidator que foi referenciada na anotação @Constraint.

A classe de validação implementa ConstraintValidator e precisa sobrescrever o método isValid, que o método que vai considerar se o valor recebido através do parâmetro value é valido ou não.

import com.google.common.base.Strings;

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

public class StringAsBigDecimalValidator implements ConstraintValidator<StringAsBigDecimalValid, String> {

    private String value;

    @Override
    public void initialize(StringAsBigDecimalValid constraintAnnotation) {
        this.value = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if(Strings.isNullOrEmpty(value)) {
            return true;
        }

        try {
            new BigDecimal(value);
            return true;
        } catch (NumberFormatException nfex) {
            return false;
        }
    }
}

No caso do exemplo, caso o valor vir nulo ou vazio é considerado válido, porque ele não é obrigatório, mas caso vir um valor deve ser possível criar um BigDecimal através do construtor, senão será lançado uma NumberFormatException e classificado o valor como inválido.

Finalizado as implementações, agora é apenas utilizar a anotação no atributo desejado.


import org.hibernate.validator.constraints.NotEmpty;

public class ExcelMapper {

    @NotEmpty(message = "O código é obrigatório")
    private String codigo;

    @StringAsBigDecimalValid(message = "Valor inválido")
    private String valor;

    public ExcelMapper(String codigo, String valor) {
        this.codigo = codigo;
        this.valor = valor;
    }
....
}

Para enfatizar o funcionamento da anotação StringAsBigDecimalValid, criei três casos de testes para validar o cenário de validação.

import org.junit.Test;
import org.junit.Assert;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

public class ExcelMapperTest {

   final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

   // Validação Ok, a String 10 é um valor válido de BigDecimal
   @Test
   public void naoDeveRetornarErroComLinhaComCodigoEValor() {
       final ExcelMapper linhaExcel = new ExcelMapper("1", "10");
       final Set<ConstraintViolation<ExcelMapper>> constraintViolations = validator.validate(linhaExcel)
       Assert.assertTrue(constraintViolations.isEmpty());
   }

  // Validação Ok, a validação não considera null como inválido
   @Test
   public void naoDeveRetornarErroComLinhaComApenasCodigo() {
       final ExcelMapper linhaExcel = new ExcelMapper("2", null);
       final Set<ConstraintViolation<ExcelMapper>> constraintViolations = validator.validate(linhaExcel)
       Assert.assertTrue(constraintViolations.isEmpty());
   }

   // Erro de validação, a String AAA não é um valor válido de BigDecimal, pois não é um decimal
   @Test
   public void deveRetornarErroComValorInvalido() {
       final ExcelMapper linhaExcel = new ExcelMapper("3", "AAAA");
       final Set<ConstraintViolation<ExcelMapper>> constraintViolations = validator.validate(linhaExcel)
       Assert.assertFalse(constraintViolations.isEmpty());
       Assert.assertEquals(1, constraintViolations.size());
       Assert.assertEquals("Valor inválido", constraintViolations.stream().findAny().get().getMessage());
   }
}

Observação: É possível instanciar um Validator através da factory buildDefaultValidatorFactory para simular uma validação de constraints de Bean Validations.

Com isso, é possível ver a simplicidade de criar nossas validações customizadas no Bean Validation, apenas criando uma anotação para marcar os atributos para validação e uma classe para implementar a validação podemos reutilizar essa garantia de consistência de dados em qualquer bean.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s