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:

  • Conjunto de componentes desenvolvidos com customizações (tabelas, listas, entrada de valores, etc.);
  • total suporte ao padrão das APIs Ajax do JSF 2;
  • não necessita de configurações iniciais para utilização;
  • sem necessidades de .jars adicionais, entre outros.

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:

  • Java EE 7 Web;
  • Maven;
  • JSF 2.2 (JSF-API 2.2.8 e JSF-IMPL 2.2.8);
  • Primefaces 5.1;
  • Wildfly 8.1.0 Final.

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:

  • @Size;
  • @Min;
  • @Max;
  • @DecimalMax;
  • @Digits;
  • @Pattern;
  • @Past;
  • @Future;
  • @Size;
  • @NotNull.

<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 "{cpf_invalido}";
 
    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() {cpf_invalido}. 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 = "{cpf_invalido}";
 
    /**
     *
     * @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'] = '{0}: \'{1}\' 
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.

Validação com Primefaces CSV
Figura 1. Validação com Primefaces CSV
Bean Validation com JSF2
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 ;)