Integrando Primefaces CSV com a Bean Validation

Neste artigo vamos mostrar ao leitor como validar dados utilizando Primefaces Client Side Validation e a Bean Validation, criando anotações próprias para que restrições anotadas em suas classes possam ser validadas no servidor ou no cliente.

O Primefaces é hoje o principal framework para a construção de interfaces ricas com JSF (Java Server Faces). Tal fato deve-se a grandes facilidades que a mesma oferece dentre muitas que podemos citar:

O que é Bean Validation?

Bean Validation (BV) especificação JSR-303 - é um modelo de validação de dados disponível como parte da plataforma Java EE 6, permitindo utilizar anotações como restrições a nível de métodos, atributos ou até mesmo classes.

O JSF 2.2 suporta a Bean Validation naturalmente, exceto no nível de classe. Entretanto, toda validação é feita do lado do servidor, com o Primefaces esta pode ser feita no cliente, poupando alguns recursos e otimizando a experiência do usuário com a aplicação.

Configuração do projeto: Primefaces CSV com a Bean Validation

Para referência, o projeto foi construído e testado com as seguintes tecnologias:

Será utilizada neste artigo a versão 5.1 do Primefaces e que pode ser baixada diretamente do site principal. Se o projeto utilizar o Maven, o .jar pode ser obtido adicionando o repositório no pom.xml,conforme a Listagem 1 e a dependência exibida na Listagem 2.

<repositories> <!-- Adicione o repo abaixo ou adicione-o na sua seção <repositories/> --> <repository> <id>prime-repo</id> <name>PrimeFaces Maven Repository</name> <url>http://repository.primefaces.org</url> </repository> </repositories>
Listagem 1. Adicionar o repositório
<dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>5.1</version> </dependency>
Listagem 2. Dependência do Primefaces 5.1

O próximo passo é ativar a CSV do framework e permitir transformar metadados da Bean Validation em atributos HTMLM. É necessário adicionar o código presente na Listagem 3 ao arquivo web.xml da aplicação. Com esta configuração, as anotações abaixo também serão processadas no lado do cliente:

<context-param> <param-name>primefaces.CLIENT_SIDE_VALIDATION</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>primefaces.TRANSFORM_METADATA</param-name> <param-value>true</param-value> </context-param>
Listagem 3. Configuração adicional no web.xml

Criando uma anotação

O artigo contemplará a criação da anotação @CPF. A interface para validar um atributo como um valor CPF aparece descrita na Listagem 4.

package com.devmedia.artigo.validator; import com.devmedia.artigo.constraint.CPFClientValidationConstraint; import com.devmedia.artigo.constraint.CPFConstraintValidator; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; import org.primefaces.validate.bean.ClientConstraint; /** * * @author luiz */ @Documented @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RUNTIME) @Constraint(validatedBy = CPFConstraintValidator.class) @ClientConstraint(resolvedBy = CPFClientValidationConstraint.class) public @interface CPF { String message() default ""; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Listagem 4. @CPF

A Tabela 1 contém informações sobre as anotações utilizadas.

Anotação Descrição
@Documented Indicar que a anotação criada deve ter a documentação gerada pelo JavaDoc ou ferramenta similar.
@Target({ElementType.METHOD, ElementType.FIELD}) Informar o contexto em que a anotação pode ser utilizada. No caso em específico, na declaração de métodos ou atributos.
@Retention(RUNTIME) Significa que a anotação estará disponível em tempo de execução por meio de reflexão.
@Constraint(validatedBy= CPFConstraintValidator.class) Especifica qual classe será responsável por fazer a validação desta anotação no servidor.
@ClientConstraint(resolvedBy= CPFClientValidationConstraint.class) Faz parte do Primefaces CSV e ajudará a resolver metadados.
Tabela 1. Anotações

Os elementos message(), groups() e payload() são obrigatórios e definidos padrões para cada um deles, conforme Tabela 2.

Método Valor padrão Descrição
message() . Definir uma mensagem ou a chave padrão para uma mensagem quando o cpf não é válido. O valor da chave deve ficar localizado no arquivo ValidationMessages.properties.
groups() Array vazio faz pertencer ao grupo javax.validation.Default Permitir que um grupo de anotações sejam processadas durante a validação de um objeto, útil também para controlar a ordem em que são avaliadas.
payload() Array vazio. Tipicamente utilizados para conter informações de metadados.
Tabela 2. Elementos obrigatórios

A Listagem 5 define o conteúdo do arquivo ValidationMessages.properties, caso não exista no projeto, deverá estar localizada no diretório /src/main/resources/.

cpf_invalido=Erro: CPF inválido.
Listagem 5. ValidationMessages.properties

Faz-se necessário criar a classe que fará a validação do CPF, conforme informado na anotação @Constraint, que deve implementar a ConstraintValidator. O código pode ser conferido na Listagem 6.

package com.devmedia.artigo.constraint; import com.devmedia.artigo.validator.CPF; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; /** * * @author luiz * @version 0.1 Esta classe implementa Validação para a anotação @CPF. * O valor do CPF pode ser uma String formatada ou não. */ public class CPFConstraintValidator implements ConstraintValidator<CPF, String> { private CPF icpf; /** * * @param icpf @CPF */ @Override public void initialize(CPF icpf) { this.icpf = icpf; } /** * * @param cpf valor do cpf a ser verificado. * @param cvc utilizado para construir template com mensagem do erro. * @return se o CPF é válido ou não. */ @Override public boolean isValid(String cpf, ConstraintValidatorContext cvc) { if (cpf == null || cpf.equals("")) { return true; } cvc.buildConstraintViolationWithTemplate(icpf.message()).addConstraintViolation() .disableDefaultConstraintViolation(); cpf = cpf.replaceAll("[^0123456789]", ""); if (isCPFPadrao(cpf) || cpf.length() != 11) { return false; } int dv1 = calcDVs(cpf.substring(0, 9), PESO_CPF); int dv2 = calcDVs(cpf.substring(0, 9) + dv1, PESO_CPF); return cpf.equals(cpf.substring(0, 9) + dv1 + dv2); } private static final int[] PESO_CPF = {11, 10, 9, 8, 7, 6, 5, 4, 3, 2}; private static boolean isCPFPadrao(String cpf) { return cpf.equals("00000000000") || cpf.equals("11111111111") || cpf.equals("22222222222") || cpf.equals("33333333333") || cpf.equals("44444444444") || cpf.equals("55555555555") || cpf.equals("66666666666") || cpf.equals("77777777777") || cpf.equals("88888888888") || cpf.equals("99999999999"); } private static int calcDVs(String cpf, int[] peso) { int soma = 0; for (int indice = cpf.length() - 1, digito; indice >= 0; indice--) { digito = Integer.parseInt(cpf.substring(indice, indice + 1)); soma += digito * peso[peso.length - cpf.length() + indice]; } int resto = (soma * 10) % 11; return (resto == 10 || resto == 11) ? 0 : resto; } }
Listagem 6. CPFConstraintValidator.java

O método initialize() é responsável por carregar o objeto icpf, que obtém, por exemplo, a mensagem de erro em caso de falha durante o processo de validação. Já isValid() deve retornar um booleano indicando se o valor do atributo que representa o CPF é válido. Os demais métodos e atributos auxiliam na criação da lógica do processo de validação.

Embora não obrigatório, é uma boa prática que valores nulos sejam interpretados como válidos, pois caso contrário, se o mesmo for anotado com @NotNull, este ficaria sem efeito.

Desenvolvendo a validação no lado do Cliente

O próximo passo é construir a validação client-side, primeiramente será escrita a classe informada pelo atributo resolvedBy na anotação @ClientConstraint da Listagem 4. Obrigatoriamente ela deve implementar ClientValidationConstraint e o código necessário está descrito na Listagem 7.

package com.devmedia.artigo.constraint; import com.devmedia.artigo.validator.CPF; import java.util.HashMap; import java.util.Map; import javax.validation.metadata.ConstraintDescriptor; import org.primefaces.validate.bean.ClientValidationConstraint; /** * * @author luiz * @version 0.1 Esta classe implementa ClienteValidationConstraint do Primefaces * para a anotação @CPF. */ public class CPFClientValidationConstraint implements ClientValidationConstraint { private static final String MESSAGE_ID = ""; /** * * @param constraintDescriptor Descriptor de @interface CPF. * @return Map de metadados a serem utilizados na validação do cliente. */ @Override public Map<String, Object> getMetadata(ConstraintDescriptor constraintDescriptor) { Map<String, Object> metadata = new HashMap<String, Object>(); Map attrs = constraintDescriptor.getAttributes(); String message = (String) attrs.get("message"); if (!message.equals(MESSAGE_ID)) { metadata.put("data-p-cpf-msg", message); } return metadata; } /** * * @return retorna o ID do Validador */ @Override public String getValidatorId() { return CPF.class.getSimpleName(); } }
Listagem 7. CPFClientValidationConstraint.java

O método getMetadata() retorna metadados que serão utilizados na validação client-side. O método getValidatorId() deve retornar uma String representando um identificador único que é utilizado para ser associado a um validador.

A Listagem 8 mostra a implementação da validação em JavaScript. Primeiramente é necessário criar dentro do diretório web um arquivo chamado validators.js. No projeto, o arquivo foi adicionado em /src/main/webapp/resources/js/.

PrimeFaces.locales['en_US'].messages['cpf_invalido'] = ': \'\' não pode ser entendido como um cpf válido.'; PrimeFaces.validator['CPF'] = { MESSAGE_ID: 'cpf_invalido', validate: function (element, value) { if (value !== null && value !== "") { var vc = PrimeFaces.util.ValidationContext; if (!validarCPF(value)) { var label = element.data('p-label'); var msgStr = element.data('p-cpf-msg'), msg = msgStr ? {summary: msgStr, detail: msgStr} : vc.getMessage(this.MESSAGE_ID, label, value); throw msg; } } } }; function validarCPF(cpf) { var div1, div2; cpf = cpf.replace(/[^\d]+/g, ''); // Verifica tamanho e se é um cpf padrão if (cpf.length !== 11 || isCPFPadrao(cpf)) return false; div1 = calcDVs(cpf.substring(0, 9)); div2 = calcDVs(cpf.substring(0, 9) + div1); return (cpf == (cpf.substring(0, 9) + div1 + div2)); } ; function isCPFPadrao(cpf) { if (cpf == "00000000000" || cpf == "11111111111" || cpf == "22222222222" || cpf == "33333333333" || cpf == "44444444444" || cpf == "55555555555" || cpf == "66666666666" || cpf == "77777777777" || cpf == "88888888888" || cpf == "99999999999") return true; else return false; } ; function calcDVs(cpf) { var soma, digito, resto; var peso = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]; soma = 0; for (indice = cpf.length - 1; indice >= 0; indice--) { digito = parseInt(cpf.substring(indice, indice + 1)); soma += digito * peso[peso.length - cpf.length + indice]; } resto = (soma * 10) % 11; return (resto == 10 || resto == 11) ? 0 : resto; }
Listagem 8. Validator.js

A primeira linha define uma mensagem padrão para o validador. Esta mensagem será mostrada caso o valor de message() da anotação @CPF não seja substituído, conforme é explicado posteriormente no código da Listagem 9 (linhas 16-19). O Primefaces CSV tem o inglês (en_US) como idioma padrão para customização das mensagens.

A terceira linha registra o validador com id 'CPF' no Primefaces. É importante observar que o método getValidatorId() da Listagem 7 também deverá retornar este mesmo nome.

A função validate() faz a validação do valor do CPF no cliente e recebe dois argumentos: o primeiro é o próprio elemento HTML e o segundo o valor presente dentro do campo. Os elementos retornados pelo método getMetaData() presente na Listagem 7 podem ser acessados na função validate() por meio do uso do element.data('nome'), em que element é um objeto jQuery e nome é a chave do atributo (sem o prefixo 'data-').

A Listagem 9 exibe uma classe chamada “Pessoa” que utiliza @CPF e mais algumas: @NotNull e @Min.

package com.devmedia.artigo.entity; import com.devmedia.artigo.validator.CPF; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; /** * * @author luiz */ public class Pessoa { @NotNull private String nome; @NotNull @CPF // Utilize @CPF(message = “...”) para modificar a mensagem de exibição. // Exemplo: @CPF(message = “O CPF deste Usuário não é válido”) private String cpf; @Min(18) private int idade; public Pessoa() { } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getCpf() { return cpf; } public void setCpf(String cpf) { this.cpf = cpf; } public int getIdade() { return idade; } public void setIdade(int idade) { this.idade = idade; } }
Listagem 9. Classe Pessoa

A Listagem 10 exibe um Named Bean para manipulação do cadastro de uma Pessoa.

package com.devmedia.artigo.bean; import com.devmedia.artigo.entity.Pessoa; import java.util.Set; import javax.annotation.PostConstruct; import javax.enterprise.context.RequestScoped; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.inject.Named; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; /** * * @author luiz */ @Named @RequestScoped public class PessoaBean { private Pessoa pessoa; @PostConstruct public void init() { pessoa = new Pessoa(); } public Pessoa getPessoa() { return pessoa; } public void salvarCSV() { FacesContext fc = FacesContext.getCurrentInstance(); fc.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Primefaces CSV", "Registro salvo com sucesso")); pessoa = new Pessoa(); } public void salvar() { Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set<ConstraintViolation<Pessoa>> violations = validator.validate(pessoa); FacesContext fc = FacesContext.getCurrentInstance(); if (violations.isEmpty()) { fc.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Salvo", "Registro salvo com sucesso")); pessoa = new Pessoa(); } else{ for (ConstraintViolation cv : violations) { fc.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, cv.getMessage(), null)); } } } }
Listagem 10. PessoaBean.java

O método salvar() faz a validação no lado do servidor utilizando a classe javax.validation.Validator. O método validate() retorna um Set com todas as ConstraintsViolations encontradas durante a validação do objeto “pessoa”: se o Set estiver vazio, logicamente nenhum erro de validação foi encontrado e o objeto pode ser salvo. O método salvarCSV() apenas exibe a mensagem de um objeto sendo salvo com sucesso, pois a validação ocorre antes, no lado cliente.

A Listagem 11 exibe a view criada para o cadastro. O arquivo validators.js deve ser importado, e a validação utilizando CSV é ativada no primeiro componente <p:commandbutton> através da ativação da tag “validateClient=true”, enquanto o segundo botão faz a submissão e valida os dados no servidor.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:p="http://primefaces.org/ui"> <h:head> <h:outputScript library="js" name="validators.js"/> <title>Primefaces CSV</title> </h:head> <h:body> <h:form id="form"> <p:growl id="infos" severity="info" showDetail="true"/> <p:messages id="errors" severity="warn,error"/> <h1>Cadastro</h1> <div class="ui-grid ui-grid-responsive"> <div class="ui-grid-row"> <div class="ui-grid-col-1"> <p:outputLabel value="Nome" for="nome"/> </div> <div class="ui-grid-col-1"> <p:inputText id="nome" value="#{pessoaBean.pessoa.nome}"/> </div> </div> <br/> <div class="ui-grid-row"> <div class="ui-grid-col-1"> <p:outputLabel value="CPF" for="cpf"/> </div> <div class="ui-grid-col-1"> <p:inputMask id="cpf" value="#{pessoaBean.pessoa.cpf}" mask="999.999.999-99"/> </div> </div> <br/> <div class="ui-grid-row"> <div class="ui-grid-col-1"> <p:outputLabel value="Idade" for="idade"/> </div> <div class="ui-grid-col-1"> <p:inputText id="idade" value="#{pessoaBean.pessoa.idade}"/> </div> </div> <br/> <div class="ui-grid-row"> <div class="ui-grid-col-2"> <p:commandButton action="#{pessoaBean.salvarCSV}" value="Primefaces CSV" validateClient="true" ajax="false" update="errors infos"/> </div> <div class="ui-grid-col-2"> <p:commandButton ajax="false" action="#{pessoaBean.salvar}" value="Sem Primefaces CSV" update="errors infos"/> </div> </div> </div> </h:form> </h:body> </html>
Listagem 11. View de cadastro

A Figura 1 mostra o resultado de erro de validação do campo CPF utilizando CSV enquanto a Figura 2 exibe o erro utilizando JSF 2 com a Bean Validation.

Figura 1. Validação com Primefaces CSV
Figura 2. Bean Validation com JSF2

Pode ser feito o questionamento pelo leitor quanto à necessidade de validar dados no front-end e/ou na aplicação. A validação somente no cliente pode ser facilmente manipulada e/ou ignorada causando inconsistências no banco de dados, porém traz feedback muito mais agradável ao usuário e pode custar menos tráfego de rede, sendo feita no servidor conferirá ao projeto uma segurança maior no processamento dos dados.

Realizar a validação em ambos os lados é de fato simples e bastaria existir um <p:commandbutton> semelhante ao da Listagem 12.

<p:commandButton ajax="false" validateClient=”true” action="#{pessoaBean.salvar}" value="Salvar" update="errors infos"/>
Listagem 12. Validação cliente e servidor

Portanto, ambas as técnicas podem ser utilizadas conjuntamente.

Links:

Links Úteis

  • Números mágicos: O que são e como corrigir: Confira o que são números mágicos e como reconhecer a presença dessa má prática de programação em códigos.
  • O que é Git?: Neste curso você aprenderá o que é o Git, ferramenta para controle de versão mais utilizada por desenvolvedores de software.

Saiba mais sobre Bean Validation e Primefaces ;)

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados