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>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>5.1</version>
</dependency>
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>
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 {};
}
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. |
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. |
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.
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;
}
}
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();
}
}
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;
}
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;
}
}
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));
}
}
}
}
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>
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.
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"/>
Portanto, ambas as técnicas podem ser utilizadas conjuntamente.
- Primefaces
- Bean Validation, Emmanuel Bernard
- JSR 303 Bean Validation – Especificação final, Emmanuel Bernard
- Primefaces User Guide 5.1, Çağatay Çivici
- Algoritmo do CPF
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 ;)
- Introdução ao PrimeFaces: Neste artigo veremos o que é e como utilizar o framework PrimeFaces em um projeto web baseado em JavaServer Faces, a fim de criar uma rica experiência de usabilidade aos usuários.
- Bean Validation: Validação de dados em Java: Neste curso você aprenderá a implementar validação com Bean Validation.
- Bean Validation 1.1: validando dados com anotações: Aprenda com este artigo a usar todo o poder das anotações para validar campos, métodos e classes com a JSR 349.