Conversores JSF é o tipo de coisa que a gente que programa com JavaServer Faces precisa aprender bem.
Não gosto de dizer que alguém “precisa” aprender algo, principalmente quando o assunto é programação, mas conversores é algo que necessitamos com frequência.
Algumas dores de cabeça, com certeza, poderão ser evitadas por saber como utilizar um Converter.
Na verdade, em JSF usamos conversores o tempo todo, mesmo sem notar.
No artigo de hoje, você vai aprender tudo o que precisa saber sobre conversores, para integrar melhor seu modelo com os componentes JSF.
Continue lendo para aprender mais sobre:
- Porque os conversores são necessários
- Quais os conversores padrão do JSF que você já usa mesmo sem querer
- Como usar o DateTimeConverter
- Como usar o NumberConverter
- Como criar seu próprio Converter JSF
Gostou do assunto?
Vamos começar.
Por que os conversores são necessários?
A web é toda baseada em texto. Não existem números, não existem booleans, não existem enums, é tudo texto.
Por isso, é importante ter uma arquitetura que possua flexibilidade entre o momento de exibir uma informação (na tela do sistema, por exemplo) e o de captar algum dado (submissão total do formulário ou ajax).
O JSF implementa essa parte da arquitetura através da interface javax.faces.convert.Converter.
Uma simples propriedade do tipo java.lang.Integer em seu formulário JSF tem o seu conversor, nesse caso, o javax.faces.convert.IntegerConverter.
Isso porque não mostramos um Integer para o usuário e sim um texto que contenha o dado numérico.
Se até um Integer precisa ser convertido em texto para ser apresentado aos usuários, imagine então os objetos cheios de propriedades que precisamos criar em nosso modelo de dados.
Por isso a interface javax.faces.convert.Converter é tão útil e importante para quem desenvolve em JSF.
Conversores padrão do JSF
Vimos que uma simples propriedade Integer em seu formulário tem seu próprio conversor padrão.
Você já deve então ter imaginado e se perguntado: “E os outros tipos? Long? Boolean? Byte?”
Cada um desses aí também possui seu próprio conversor.
Aqui está a lista completa deles pra conferir:
- BigDecimalConverter
- BigIntegerConverter
- BooleanConverter
- ByteConverter
- CharacterConverter
- DateTimeConverter
- DoubleConverter
- EnumConverter
- FloatConverter
- IntegerConverter
- LongConverter
- NumberConverter
- ShortConverter
O legal dos conversores padrão é que não precisamos nos preocupar em configurá-los para uso.
Como usar um conversor padrão?
Os conversores padrão são invocados automaticamente.
Quando o formulário é submetido, o JSF relaciona o parâmetro da requisição HTTP com a propriedade que está dentro do seu managed bean.
Ele consegue fazer essa relação por duas informações que ele tem:
- a página em que está dando o post (seja pela submissão total ou com ajax)
- e claro, pelo nome da propriedade
Conhecendo a relação entre o parâmetro e propriedade, o JSF sabe qual o conversor padrão ele tem que aplicar. Seria bem trabalhoso configurar um conversor para cada componente da nossa página.
Uma coisa bacana é que você pode criar um conversor customizado e configurar para que ele seja utilizado da mesma forma que um conversor padrão, ou seja, de forma transparente.
Como essa é uma parte mais avançada, falarei mais para o final do artigo.
Por ora, vamos continuar aqui com a parte das mensagens de erro dos conversores.
Mensagens de erro dos conversores padrão
Você já deve ter visto a submissão de um campo Integer, por exemplo, com letras ao invés de números, e acabou recebendo a seguinte mensagem:
“id_do_seu_componente: ‘AXYZ’ deve ser um número formado por um ou mais dígitos”
Ou mesmo a versão em inglês pra quem ainda não configurou o locale em pt_BR.
“id_do_seu_componente: ‘AXYZ’ must be a number consisting of one or more digits”
Como já deve imaginar, cada um dos conversores padrão possui uma mensagem associada a eles.
O legal é que elas podem ser customizadas.
Para isso você precisa criar e configurar seu próprio arquivo de propriedades com suas mensagens personalizadas. Vamos chamá-lo aqui de Mensagens.properties.
Para sobrescrever as mensagens com o seu próprio arquivo, basta que você use a mesma chave utilizada pelo conversor ao adicionar a FacesMessage.
Assim, o JSF conseguirá reconhecer que você as sobrescreveu.
Para descobrir as chaves, basta que você encontre o JAR referente a API do JSF, seria algo como javax.faces-2.x.x.jar.
Dentro do pacote javax.faces do JAR, você vai encontrar o arquivo Messages.properties:
Na sessão do arquivo que fala sobre os Converter Errors você vai procurar e copiar a chave da mensagem que deseja customizar.
Só uma observação aqui:
Alterar o arquivo de mensagem que está na própria API do JSF (referente ao locale da sua aplicação) já é suficiente para funcionar, mas não recomendo que se faça isso, porque sempre que a versão da API JSF utilizada for alterada, você terá que alterar os arquivos de mensagens também.
Pior ainda pra quem usa Maven, pois, sempre que compilarem o projeto em uma máquina diferente, precisarão da alteração.
Agora que você sabe onde estão as chaves que o JSF usa, basta copiá-las para o seu arquivo Mensagens.properties e colocar o texto que desejar.
A localização do arquivo pode ser o pacote que quiser ai dentro do seu projeto.
Se sua intenção fosse, por exemplo, alterar a mensagem padrão de conversão do tipo Integer, então o arquivo Mensagens.properties ficaria assim:
javax.faces.converter.IntegerConverter.INTEGER=O componente ''{2}'' aceita somente números. Valor inv\u00e1lido: ''{0}''
Feito isso, você ainda precisa avisar o JSF que tem um arquivo de mensagens:
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2> <application> <message-bundle>com.algaworks.exemplos.jsf.conversores.Mensagens</message-bundle> </application> </faces-config>
A partir de agora o JSF dará preferência para o seu arquivo de mensagens quando buscar pela chave referente à mensagem de erro para o tipo Integer:
DateTimeConverter e NumberConverter
Pelas possibilidades de formatação que temos em relação a números e datas, temos dois conversores padrão:
- DateTimeConverter
- NumberConverter
Que possuem suas próprias tags, são elas:
- f:convertDateTime
- f:convertNumber
Veremos melhor sobre elas agora.
DateTimeConverter
Para o conversor DateTimeConverter, basta incluir a tag f:convertDateTime dentro do componente vinculado a um atributo do tipo java.util.Date.
Veja:
<outputText value="#{produtoBean.produto.dataLancamento}"> <convertDateTime timeZone="#{usuarioLogadoBean.timeZone}" locale="#{usuarioLogadoBean.locale}" pattern="dd/MM/yyyy HH:mm:ss" /> </h:outputText>
Nesse exemplo utilizei somente 3 atributos:
- timeZone
- pattern
- locale
Isso porque acredito que são os três mais importantes.
O atributo timeZone é útil para mostrarmos o mesmo momento em fusos horários diferentes. Por exemplo, enquanto no Brasil veríamos 12:00hs no sistema, no Japão seria 00:00hs.
Já o atributo pattern é mais intuitivo, porque o próprio padrão já se parece com uma data, e também porque é a mesma configuração que utilizamos com a classe SimpleDateFormat.
Só para não ficar sem explicação, ele serve para definirmos qual o modelo de formatação em que desejamos exibir a data.
Sobre o atributo locale, no exemplo acima ele não tem impacto, pois, não existem palavras a serem traduzidas, mas terão momentos em que ele será preciso.
Para entendermos melhor, veja uma lista de exemplos para o atributo pattern:
- dd.MM.yy: 01.10.16
- yyyy.MM.dd G ‘às’ hh:mm:ss z: 2016.10.01 d.C. às 08:00:00 BRT
- EEE, MMM d, ”yy: Seg, out 1, ’16
- h:mm a: 8:00 AM
- H:mm: 8:00
- H:mm:ss:SSS: 8:00:35:145
- K:mm a,z: 8:00 AM,BRT
- yyyy.MMMMM.dd GGG hh:mm aaa: 2016.Outubro.01 d.C. 08:00 AM
Repare que os nomes dos meses acima estão em português.
Isso vai acontecer se você tiver o locale padrão como pt_BR ou se você configurá-lo em seu componente através do atributo locale.
NumberConverter
Da mesma forma que na conversão de datas, para números, você vai incluir a tag f:convertNumber dentro de um componente quando o precisar formatar.
Veja:
<h:outputText value="#{pagamentoBean.transacao.valor}"> <f:convertNumber type="currency" locale="#{usuarioLogadoBean.locale}" /> </h:outputText>
No exemplo acima utilizamos type e locale para apresentar um valor monetário.
Note que o locale irá ditar qual o símbolo da moeda (R$ para pt_BR) e qual o separador de decimais (vírgula, para pt_BR).
Observe:
- pt_BR: R$ 1.500,00
- en_US: $ 1,500.00
A tag f:convertNumber também possui um atributo pattern.
Apesar do jeito acima ser o mais conveniente para exibir valores monetários (com h:outputText, por exemplo), o atributo pattern seria bem útil para usarmos em um h:inputText, por exemplo.
Isso porque se o type igual a currency for utilizado em uma entrada de texto, teríamos que informar, além do número, o símbolo.
Imaginando que quiséssemos informar o valor 1500, precisaríamos ter no h:inputText a string “R$ 1500”. Seria bem incomodo ter que colocar o R$, não acha? Esse comportamento dificultaria até para incluir no futuro uma máscara de digitação.
Com o atributo pattern, e omitindo o type (que tem o padrão como number) temos uma configuração mais interessante para as entradas de texto.
Seria assim:
<h:inputText value="#{produtoBean.produto.preco}"> <f:convertNumber pattern="#,##0.00" locale="#{usuarioLogadoBean.locale}" /> </h:inputText>
Explicando melhor cada parte do padrão de formatação usado:
- 00: Aqui nós dizemos que queremos duas casas decimais
- .: O ponto serve para mostrarmos onde é a separação de inteiro e decimais. Para configurar o pattern, é utilizado o ponto mesmo se o locale for pt_BR
- #,##0: Essa parte serve para mostrar que desejamos fazer agrupamento, ou seja, separar de 3 em 3. Se será utilizado ponto ou vírgula, vai depender do locale
Sobre o 0 (zero) e o # (sharp), o 0 quer dizer que o espaço deve ser preenchido de qualquer forma.
No caso acima, se o número tiver somente uma casa decimal, então, o formatador do conversor deve preencher a segunda casa com zero.
O # quer dizer que se existe o número, então ele deve ser preenchido, caso não, então deixe vazio.
Veja exemplos:
- O número 0.0 com o modelo #,##0.00 imprime 0,00;
- O número 0.0 com o modelo #,###.00 imprime ,00;
- O número 15500.0 com o modelo #,###.00 imprime 15.500,00;
- O número 15500.0 com o modelo 000000.## imprime 015500;
Além dos atributos que mostrei (type, locale, pattern) é legal que você conheça mais cinco:
- maxFractionDigits
- maxIntegerDigits
- minFractionDigits
- minIntegerDigits
- currencySymbol
- currencyCode
Acredito que os 4 primeiros são intuitivos, mas para também termos um exemplo, você poderia utilizá-los assim:
<h:inputText value="#{pagamentoBean.transacao.valor}"> <f:convertNumber minFractionDigits="2" locale="#{usuarioLogadoBean.locale}" /> </h:inputText>
Inclusive o exemplo acima seria um alternativa ainda mais simples para valores monetários, pois evita a configuração do template de formatação do pattern.
Note que não precisamos informar que queremos fazer agrupamento, mas isso já é feito por padrão.
Ele tem o mesmo efeito do exemplo exibido imediatamente antes desse, onde utilizamos o atributo pattern.
Quanto ao currencyCode e currencySymbol, eles tem o mesmo propósito, que é o de indicar explicitamente qual é a moeda (através do símbolo).
A diferença é que no currencySymbol você informa uma string qualquer e em currencyCode devemos informar qual o código da moeda de acordo com a ISO 4217.
A interface Converter
Vamos analisar agora a interface javax.faces.convert.Converter e depois vamos implementar um exemplo.
Veja:
public interface Converter { public String getAsString(FacesContext context, UIComponent component, Object value); public Object getAsObject(FacesContext context, UIComponent component, String value); }
Lembra que falamos que toda informação que chega ao usuário final vai como um texto?
Pois então, essa é a função de getAsString, a String que ele retornar será utilizada pelo componente JSF para ser exibida na tela do usuário.
O método getAsObject é o contrário. Ele recebe a String que o usuário informou em campos de entrada e a transforma em um objeto que seja compatível com o tipo da propriedade que está vinculado pelo atributo value do componente.
Criando o próprio Converter
Iremos criar um conversor para converter a classe Telefone abaixo em uma string e vice-versa:
public class Telefone { private String codigoPais; private String codigoArea; private String numero; //getters e setters omitidos }
O resultado da classe Telefone convertida em string deve ser algo como +55 (11) 26269415.
Para isso temos o converter:
public class TelefoneConverter implements Converter { public String getAsString(FacesContext context, UIComponent component, Object value) { if (value == null) { return null; } Telefone telefone = (Telefone) value; return "+" + telefone.getCodigoPais() + " (" + telefone.getCodigoArea() + ") " + telefone.getNumero(); } public Object getAsObject(FacesContext context, UIComponent component, String value) { if (value == null) { return null; } // de +55 (11) 2626-9415 para 551126269415 String somenteNumeros = StringUtils.deixarSomenteDigitos(value); try { String codigoPais = somenteNumeros.substring(0, 2); String codigoArea = somenteNumeros.substring(2, 4); String numero = somenteNumeros.substring(4); return new Telefone(codigoPais, codigoArea, numero); } catch (IndexOutOfBoundsException e) { FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Problemas na conversão do telefone.", "Ele deve ser informado com código do país, de área e o número. Ex: +55 (11) 2626-9415"); throw new ConverterException(facesMessage); } } }
Nosso converter está pronto. Agora temos que ver como podemos configurá-lo.
Como registrar o conversor
Podemos fazer isso tanto através da anotação @FacesConverter:
@FacesConverter("telefoneConverter") public class TelefoneConverter implements Converter { ... }
…quanto via xml no faces-config.xml:
<converter> <description>Configurando converter</description> <converter-id>telefoneConverter</converter-id> <converter-class>com.algaworks.exemplos.jsf.conversores.ex3.TelefoneConverter</converter-class> </converter>
Depois de registrado, basta configurá-lo no componente da nossa página XHTML.
Utilizando o telefoneConverter
Temos duas maneiras de utilizar um conversor:
- com auxilio da tag f:converter
- através do atributo converter que os componentes possuem
Utilizando a tag f:converter, é só configurar o id que demos para o nosso converter (que foi telefoneConverter) dentro da propriedade converterId da tag.
Olhe só:
<h:input value="#{contatoBean.contato.telefone}"> <f:converter converterId="telefoneConverter" /> </input>
Assim você já consegue utilizá-lo atráves da tag f:converter.
Pelo atributo converter do componente, é bem parecido. Basta incluir o id do conversor diretamente no atributo:
<h:input value="#{contatoBean.contato.telefone}" converter="telefoneConverter" />
Essas duas formas de utilizar lhe darão o mesmo resultado.
Mas ainda tem uma outra forma, você pode fazer um bind de uma propriedade do tipo TelefoneConverter que está no seu managed bean, ao invés de trabalhar com o id do conversor.
Olha como ficaria no caso da tag f:converter:
<h:input value="#{contatoBean.contato.telefone}"> <f:converter binding="#{contatoBean.telefoneConverter}" /> </input>
Dá para fazer de modo similar incluindo diretamente no atributo do componente:
<h:input value="#{contatoBean.contato.telefone}" converter="#{contatoBean.telefoneConverter}" />
Para a alternativa com o bind, não é necessário o registro do conversor.
Ainda temos uma outra forma que pode ser uma boa solução.
Como registrar um conversor transparente para nossa classe Telefone
Da forma como registramos anteriormente o conversor TelefoneConverter, precisamos configurar a tag f:converter dentro do componente, ou da propriedade converter do mesmo.
Ou seja, precisamos, explicitamente, dizer qual o conversor que o componente deve utilizar.
Mas, tem uma outra forma de registrar que torna desnecessária essa configuração. Conseguimos assim criar uma espécie de conversor transparente.
Assim ele vai funcionar da mesma forma que os conversores padrão que expliquei no início.
Fazemos isso dizendo que nosso conversor TelefoneConverter é o conversor padrão para o tipo Telefone.
Para isso precisamos alterar a anotação adicionando o atributo forClass:
@FacesConverter(forClass = Telefone.class) public class TelefoneConverter implements Converter { ... }
Lembre-se de apagar o valor da propriedade value da anotação (que representa o ID do converter). Caso você deixe os dois, a preferência será para configuração com ID.
De modo parecido, podemos configurar isso através do faces-config.xml:
<converter> <description>Configurando converter padrao para a classe Telefone</description> <converter-for-class>com.algaworks.exemplos.jsf.conversores.ex3.Telefone</converter-for-class> <converter-class>com.algaworks.exemplos.jsf.conversores.ex3.TelefoneConverter</converter-class> </converter>
Perceba que ao invés da tag converter-id, utilizamos converter-for-class.
Com essa alteração, o conversor seria aplicado mesmo que utilizássemos o componente assim:
<h:input value="#{contatoBean.contato.telefone}" />
Agora, não precisamos da tag f:converter ou da propriedade converter do próprio componente.
Componentes mais complexos e os conversores
Nem precisamos ir muito longe, o que quero falar acontece até com um simples h:selectOneMenu.
Talvez você já tenha passado pela situação de fazer um conversor, mas ficou confuso porque o componente não exibe para o usuário, aquilo que o método getAsString retorna.
Vou explicar, vamos supor que tenha a seguinte classe Cliente.java:
public class Cliente { private Integer id; private String nome; //getters e setters omitidos }
…e para essa classe você tenha o conversor abaixo:
public class ClienteConverter implements Converter { public String getAsString(FacesContext context, UIComponent component, Object value) { if (value == null) { return null; } Cliente cliente = (Cliente) value; return cliente.getId().toString(); } ... }
A questão é que os componentes que possuem uma forma de apresentar os dados um pouco mais complexa, não usam essa informação para exibir na tela.
Vejamos como seria um h:selectOneMenu de clientes:
<h:selectOneMenu value="#{clienteBean.cliente}" converter="clienteConverter"> <f:selectItems value="#{clienteBean.lista}" var="c" itemLabel="#{c.nome}" itemValue="#{c}" /> </h:selectOneMenu>
Ele renderizado ficaria:
Repare que não está sendo exibido para o usuário o ID do cliente que foi retornado pelo método getAsString, o componente claramente mostra o nome.
Para os casos como esse do h:selectOneMenu, o valor que é retornado por getAsString (o ID, no caso) é usado para que o componente saiba qual a relação entre o que está sendo exibido na tela, com o valor que o método getAsObject seu conversor espera receber, ou seja, o ID não está sendo exibido para o usuário.
Precisei falar sobre isso porque, no artigo, mencionei que o método getAsString retorna o valor que irá ser exibido para o usuário.
Mas alguns componentes usam essa informação NÃO para exibir para o usuário, e sim para saberem o que enviar quando o formulário for submetido.
Conclusão
Agora você tem todo o conhecimento que precisa para trabalhar com conversores JSF.
Vimos sobre a questão da apresentação de informações ser String-based, ou seja, tudo que é exibido para o usuário é uma String, e por isso conversores são importantes.
Mostrei como você pode criar seu próprio conversor e registrá-lo, e você também aprendeu como configurar um conversor padrão para algum tipo específico.
Espero que tenha sido útil.
Pra você que veio até aqui e deseja aprender mais sobre JSF e PrimeFaces, tem um e-book bem bacana que pode ajudar muito você. É o livro Java EE 7 com JSF, PrimeFaces e CDI:
Depois que fizer o download, deixa um comentário logo abaixo dizendo o que achou do livro ou do artigo, beleza?
Um abraço pra você e até uma próxima!
PS: Você pode acessar o código-fonte dos exemplos no nosso perfil do GitHub: http://github.com/algaworks/artigo-conversores-jsf
Olá,
o que você achou deste conteúdo? Conte nos comentários.