quarta-feira, 11 de dezembro de 2013

Destaque (highlight) do texto filtrado na coluna do Primefaces DataTable


Neste post vou mostrar como destacar na coluna do dataTable do Primefaces o texto que o usuário digita no filtro. São 5 passos bem simples, mostrados a seguir:

i) Criar no managed bean o método removeAcentos, como foi mostrado no post anterior.

ii) Fazer um binding da tabela:

1. <p:dataTable id="Tabela"
2.                              binding="#{controlador.tabela}"


1.     private DataTable tabela;
2. 
3.     public DataTable getTabela() {
4.         return tabela;
5.     }
6. 
7.     public void setTabela(DataTable tabela) {
8.         this.tabela = tabela;
9.     }

iii) Chamar um método para retornar o texto destacado, e usar escape="false":

1. <p:column headerText="Título"
2.            filterBy="#{registro.titulo}#{controlador.removeAcentos(registro.titulo)}"
3.            filterMatchMode="contains" >
4.            <h:outputText value="#{controlador.highlight(registro.titulo,'titulo}#{controlador.removeAcentos(registro.titulo)')}"
5.                            escape="false" />
6. </p:column>

O segundo parâmetro do método é o conteúdo do filterBy sem o identificador inicial e sem a chave final (ou seja, usar somente a parte destacada em verde, ignorando as partes vermelhas):

 #{registro.   titulo}#{controlador.removeAcentos(registro.titulo)    }

Este comportamento é do médoto getFilters da tabela, como poderemos ver posteriormente no quinto passo.

iv) Criar, no managed bean, o método para montar o texto destacado:

1.     public String buildHighlight(String filtro, String texto) {
2.         String retorno = "";
3.         Integer posicao = 0, tam = filtro.length();
4.         while (true) {
5.             if (posicao > texto.length()) {
6.                 break;
7.             }
8.             if ((texto.length() - posicao) < tam) {
9.                 retorno += StringUtils.substring(texto, posicao);
10.                 break;
11.             }
12.             if (removeAcentos(StringUtils.substring(texto, posicao, posicao + tam)).equalsIgnoreCase(removeAcentos(filtro))) {
13.                 retorno += <span style="color: red;'>" + StringUtils.substring(texto, posicao, posicao + tam) + "</span>";
14.                 posicao += tam;
15.             } else {
16.                 retorno += StringUtils.substring(texto, posicao, posicao + 1);
17.                 posicao++;
18.             }
19.         }
20.         return retorno;
21.     }

v) Criar, no managed bean, o método para obter o valor digitado e retornar o destaque:

1.     public String highlight(String texto, String filtro) {
2.         String retorno = "";
3.         if (texto != null) {
4.             Map<String, String> filtros = tabela.getFilters();
5.             String textoDigitado = filtros.get(filtro);
6.             if (textoDigitado != null) {
7.                 retorno = buildHighlight(textoDigitado, texto);
8.             } else {
9.                 return texto;
10.             }
11.         }
12.         return retorno;
13.     }

Veja um exemplo que filtra o texto "Aneis" (sem acentos!) e destaca o texto encontrado (com acentos!):


Este recurso foi implementado a partir da necessidade de visualizar mais facilmente palavras que são digitadas sem acento e que aparecem no texto da coluna com acento. Quando as palavras são bem específicas, a visualização é mais fácil, mas quando são palavras pequenas e o texto da coluna tem várias linhas, o destaque mostrado acima é essencial.

terça-feira, 10 de dezembro de 2013

Como ignorar acentos usando o filtro do Primefaces


Um dos mais comuns pedidos dos usuários de sistemas é que possam pesquisar dados em tabelas sem a preocupação com acentos. Há várias maneiras de implementar esse recurso, mas vou mostrar hoje uma maneira bastante simples e direta.

Dois passos simples para filtrar dados ignorando os acentos, independente de tecnologia de bancos de dados, utilizando Primefaces:

1 - Criar um método no ManagedBean para gerar uma string sem acentos:

1.     public String removeAcentos(String s) {
2.         if (s == null) {
3.             return "";
4.         }
5.         String semAcentos = s.toLowerCase();
6.         semAcentos = semAcentos.replaceAll("[áàâãä]", "a");
7.         semAcentos = semAcentos.replaceAll("[éèêë]", "e");
8.         semAcentos = semAcentos.replaceAll("[íìîï]", "i");
9.         semAcentos = semAcentos.replaceAll("[óòôõö]", "o");
10.         semAcentos = semAcentos.replaceAll("[úùûü]", "u");
11.         semAcentos = semAcentos.replaceAll("ç", "c");
12.         semAcentos = semAcentos.replaceAll("ñ", "n");
13.         return semAcentos;
14.     }

2- Utilizar o filtro do Primefaces com uma expressão que tenha a string original (com acentos) e a string sem acentos, como pode ser visto na linha 16 abaixo:

1. <p:dataTable id="Tabela" style="width: 100%; font-size: 0.95em"
2.                                  widgetVar="wTabela"
3.                                  rowIndexVar="rownumber"
4.                                  var="item"
5.                                  value="#{bean.pessoas}"
6.                                  paginator="true"
7.                                  lazy="false"
8.                                  currentPageReportTemplate="{currentPage} de {totalPages}"
9.                                  rows="10"
10.                                  pageLinks="7"
11.                                  rowsPerPageTemplate="10,20,50,100">
12. ...
13.                         <p:column sortBy="#{item.nome}"
14.                                   style="white-space: normal"
15.                                   filterMatchMode="contains"
16.                                  filterBy="#{item.nome}#{bean.removeAcentos(item.nome)}" >
17.                                   <h:outputText value="#{item.nome}" />
18.                         </p:column>

Desta maneira, podemos pesquisar tanto José como Jose, pois a comparação vai ser feita com a string "joséjose", lembrando que o Primefaces já ignora maiúsculas e minúsculas.

quarta-feira, 27 de novembro de 2013

Primefaces: atualizar componentes JSF nos clientes usando p:socket


Em uma série de posts anterior, mostrei como usar o componente poll do Primefaces para atualizar periodicamente os clientes de uma aplicação. Desta vez vou mostrar como disparar uma atualização imediata para todos os clientes usando o componente socket do Primefaces. Este componente monitora um canal de mensagens, e quando colocado em uma página, cria uma comunicação em tempo real com todos os navegadores dos clientes da aplicação. São cinco passos muito simples:

I - Incluir no projeto a biblioteca atmosphere-runtime-2.0.4.jar

Eu estou usando JBoss versão 6.1.0-Final, mas acredito que não haja problemas com outras versões.

II - Configurar o arquivo web.xml, acrescentando o seguinte trecho:

1.     <servlet>
2.         <servlet-name>Push Servlet</servlet-name>
3.         <servlet-class>org.primefaces.push.PushServlet</servlet-class>
4.     </servlet>
5.     <servlet-mapping>
6.         <servlet-name>Push Servlet</servlet-name>
7.         <url-pattern>/primepush/*</url-pattern>
8.     </servlet-mapping>

Este trecho é fixo, pode ser usado em qualquer outro projeto, sem necessidade de fazer modificações.

III - Na página que deve ser atualizada, criar um socket, pode ser no final da página, antes da tag </html>:

1.         <p:socket channel="/atualizar">
2.             <p:ajax event="message"
3.                          update=":frmPrincipal:pnlTabela"
4.                          oncomplete="wTabela.filter()" />
5.         </p:socket>

Neste exemplo, o canal de mensagens foi chamado de atualizar, o componente que será atualizado com um update é :frmPrincipal:pnlTabela (que é um p:outputPanel, no meu caso) e ainda será executado o método filter() do widget wTabela (que no meu caso é um p:dataTable).

IV - Criar um método para mandar uma mensagem por um canal:

1.     public synchronized void atualizarClientes(String canal) {
2.         PushContext pushContext =
3.                PushContextFactory.getDefault().getPushContext();
4.         pushContext.push("/"+canal, "Enviando mensagem");
5.     }

Este método pode ser colocado em alguma classe estratégica, de preferência com escopo de aplicação. Se o seu projeto tiver um façade para distribuir os serviços, lá é um bom lugar.

V - Mandar uma mensagem pelo canal desejado sempre que for necessária uma atualização:

1. atualizarClientes("atualizar");


Sempre que a chamada acima for feita, será enviada uma mensagem pelo canal atualizar. No momento, o conteúdo da mensagem não é importante, e sim o efeito causado, já que qualquer cliente da aplicação que estiver em uma página com um socket monitorando o canal atualizar, será comunicado e uma requisição ajax do evento message poderá ser disparada para qualquer fim desejado..

Observação: consulte a classe aplicacao da série anterior de posts e o método atualizarLista da mesma. A chamada atualizarClientes("atualizar") poderia ser a última linha daquele método, garantindo que sempre que houver uma operação de CRUD, será enviada uma mensagem para o canal atualizar. Lembrando que se for usada esta tecnologia com socket, o componente poll colocado nas páginas da série não será mais necessário.

quinta-feira, 3 de outubro de 2013

Atualizando DataTables dinamicamente (IV)

Este post faz parte de uma série que demonstra o uso de Primefaces, Spring, JPA, JSF para criar uma aplicação que atualiza as DataTables dos clientes automaticamente. Confira os outros posts da série aqui: Primefaces

Vamos agora introduzir um mecanismo que permite atualizar dinamicamente os p:dataTable em qualquer cliente da aplicação. Para isso vamos criar um segundo managed bean no pacote controle do projeto, chamado aplicacao:

package controle;

import delegate.FacadeBD;
import java.io.Serializable;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

@ManagedBean(name = "aplicacao")
@ApplicationScoped
public class Aplicacao implements Serializable {

    @ManagedProperty(value = "#{facadeBD}")
    private FacadeBD facadeBD;

    public Aplicacao() {
    }

    public FacadeBD getFacadeBD() {
        return facadeBD;
    }

    public void setFacadeBD(FacadeBD facadeBD) {
        this.facadeBD = facadeBD;
    }

}

Por enquanto, a única observação relevante é que o escopo deste bean é @AplicationScoped, ou seja, todos os seus atributos e métodos estão disponíveis a qualquer cliente da aplicação, a partir do momento em que o servidor ativar a aplicação.

No escopo @ViewScoped que utilizamos no bean controlador, os atributos e métodos do bean estão disponíveis separadamente para cada cliente de cada sessão. Isso implica em várias cópias (no servidor) do bean, uma para cada cliente que estiver executando a aplicação, o que causa o efeito de um cliente não ver o que o outro fez sem recorrer a uma nova leitura do banco de dados. E o pior de tudo, se quisermos manter disponível uma lista com grande quantidade de dados, haverá uma cópia da lista para cada cliente, sobrecarregando a memória do servidor.

Então, para continuar com nosso exemplo, devemos retirar do bean controlador a lista de autores e transferi-la para o bean aplicacao:

@ManagedBean(name = "aplicacao")
@ApplicationScoped
public class Aplicacao implements Serializable {

    @ManagedProperty(value = "#{facadeBD}")
    private FacadeBD facadeBD;
    private List<Autor> autores = new ArrayList<Autor>();

    public Aplicacao() {
    }

    public List<Autor> getAutores() {
        if (autores.isEmpty()) {
            autores = facadeBD.listar(Autor.class);
        }
        return autores;
    }

    public void setAutores(List<Autor> autores) {
        this.autores = autores;
    }

É óbvio que em seguida retiramos o atributo autores e os get e set correspondentes do bean controlador. Em seguida, modificamos o bean controlador, injetando o controlador aplicacao como um atributo:

public class Controlador implements Serializable {

    @ManagedProperty(value = "#{facadeBD}")
    private FacadeBD facadeBD;
    @ManagedProperty(value = "#{aplicacao}")
    private Aplicacao aplicacao;

E em seguida criamos os get e set para o atributo aplicacao:

    public Aplicacao getAplicacao() {
        return aplicacao;
    }

    public void setAplicacao(Aplicacao aplicacao) {
        this.aplicacao = aplicacao;
    }

Importante: podemos injetar um bean com escopo mais amplo em um bean com escopo mais restrito, mas não podemos fazer o contrário!

Com essa mudança, os métodos gravar e excluir devem ser modificados para apontar para o novo bean:

    public void gravar(Entidade entidade) {
        facadeBD.salvar(entidade);
        aplicacao.getAutores().clear();
    }

    public void excluir(Entidade entidade) {
        entidade.setFlagRemover(true);
        facadeBD.salvar(entidade);
        aplicacao.getAutores().clear();
    }

E a página autores.xhtml também deve ler os dados usando o novo bean:

                <p:dataTable id="Tabela" 
                             widgetVar="wTabela"
                             rowIndexVar="rowNumber"
                             rows="10"
                             paginator="true"
                             value="#{aplicacao.autores}"
                             var="registro">

Nessa fase, já eliminamos o problema de ter várias cópias da lista de autores, mantendo uma única cópia durante o tempo em que o servidor estiver disponível. Mas ainda permanece a necessidade de ler o banco de dados todas as vezes em que quisermos obter as alterações mais recentes.

Vamos corrigir isso criando três métodos no bean aplicacao:

    public void atualizarLista(Entidade e, Short operacao) {
        List l = null;
        if (e instanceof Autor) {
            l = autores;
        }
        if (l != null) {
            if (e.getId() == null) {
                l.add(e);
            } else {
                Integer indice = 0;
                Object objeto = null;
                for (Object o : l) {
                    if (((Entidade) o).getId() == e.getId()) {
                        objeto = o;
                        break;
                    }
                    indice++;
                }
                if (indice <= l.size()) {
                    if (operacao == EXCLUIR) {
                        l.remove(objeto);
                    }
                    if (operacao == ATUALIZAR) {
                        l.set(indice, e);
                    }
                }
            }
        }
    }

    public void atualizarNaLista(Entidade e) {
        atualizarLista(e, ATUALIZAR);
    }

    public void removerDaLista(Entidade e) {
        atualizarLista(e, EXCLUIR);
    }

As constantes utilizadas devem ser declaradas no início da classe:

public class Aplicacao implements Serializable {

    public static Short ATUALIZAR = 1;
    public static Short EXCLUIR = 2;
    @ManagedProperty(value = "#{facadeBD}")
    private FacadeBD facadeBD;

Esses três métodos recebem um objeto que deve pertencer a uma subclasse de Entidade e decidem se o objeto passado deve ser inserido, atualizado ou excluído da lista correspondente. Com essa mudança, os métodos gravar e excluir do bean controlador devem mudar novamente:

    public void gravar(Entidade entidade) {
        aplicacao.atualizarNaLista(autor);
        facadeBD.salvar(entidade);
    }

    public void excluir(Entidade entidade) {
        entidade.setFlagRemover(true);
        aplicacao.removerDaLista(entidade);
        facadeBD.salvar(entidade);
    }

Uma observação importante no método gravar: a lista deve ser atualizada antes do banco de dados. Caso contrário, se o registro for novo, o DAO vai mudar o valor da chave primária e o bean aplicacao não vai conseguir decidir se o registro tem que ser inserido ou alterado.

Agora, estamos com a seguinte situação: os dados são atualizados na tela de qualquer cliente, assim que o cliente fizer alguma atualização na tabela (por exemplo, gravar uma edição, ou remover um autor), mas não há mais uma nova leitura de todo o conteúdo a partir do banco de dados, porque após a operação de CRUD, apenas o objeto que foi modificado é atualizado na lista de autores.

Vamos então para o último passo: automatizar a atualização dos clientes! Isso é feito colocando um componente do Primefaces na página autores.xhtml:

            </p:dialog>
            <p:poll interval="3"  update="pnlTabela" /> 
        </h:form>
    </h:body>
</html>

O componente p:poll do Primefaces, mostrado acima, executa o update no componente pnlTabela a cada 3 segundos. Como o componente Tabela, que está dentro de pnlTabela, acessa a lista do bean aplicacao, a cada 3 segundos o cliente (seja lá quem for ou em que máquina estiver) terá uma tabela atualizada!


Para ver esse efeito, basta abrir dois navegadores, reduzi-los para metade da tela e alterar um autor em um dos navegadores. Após alguns segundos, o outro navegador atualizará automaticamente seus dados.

Não vamos nos aprofundar a respeito dos locks pessimísticos, otimísticos, concorrência, etc.. Se o leitor verificar todo o mecanismo utilizado na camada DAO e a atualização automática implementada aqui, perceberá que:
  1. duas ou mais edições ao mesmo tempo: vai prevalecer a que for gravada por último.
  2. uma edição em aberto e uma exclusão do mesmo registro, antes de gravar a edição:  os dados da edição serão descartados, já que o registro foi excluído antes da gravação.
Bem, aqui termina essa série de posts destinada a mostrar a operação básica de CRUD usando Spring, JPA, JSF e Primefaces, com atualização automática independente de estação cliente.

O código deste projeto está disponibilizado aqui: http://code.google.com/p/livraria-jsf/source/browse/Livraria
Veja o vídeo com a demonstração deste aplicativo:



Atualizando DataTables dinamicamente (III)

Este post faz parte de uma série que demonstra o uso de Primefaces, Spring, JPA, JSF para criar uma aplicação que atualiza as DataTables dos clientes automaticamente. Confira os outros posts da série aqui: Primefaces

No post anterior, criamos uma página que exibe os dados de uma entidade Autor em um componente p:dataTable do Primefaces. Vamos modificar a página criada para que ofereça as opções restantes de CRUD, ou seja, cadastrar, atualizar e excluir.

Para melhorar a aparência da aplicação, vamos criar ícones para representar algumas ações. As quatro imagens abaixo serão utilizadas para editar, excluir, gravar e adicionar.


Basta baixar as imagens (ou arranjar outras - fica à escolha de cada pessoa) e colocá-las em uma pasta do projeto chamada resources - se a pasta não existir, obviamente tem que ser criada. Eu costumo separar as imagens em uma subpasta chamada imagens, dentro de resources. Para facilitar as referências, vamos renomear as imagens para editar.png, excluir.png, gravar.png e adicionar.png, respectivamente.

Com as imagens já gravadas na pasta, vamos modificar a página autores.xhtml dessa maneira:

<?xml version='1.0' encoding='UTF-8' ?>
<!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:p="http://primefaces.org/ui"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Livraria</title>
    </h:head>
    <h:body>
        <style>
            .panelGridSemBorda tr, .panelGridSemBorda td {
                border: none;
            }
            .dialogoSemScroll div.ui-dialog-content {
                overflow: hidden;
            }
        </style>
        <h:form prependId="false" id="frmPrincipal">
            <p:outputPanel id="pnlTabela">                
                <p:dataTable id="Tabela" 
                             widgetVar="wTabela"
                             rowIndexVar="rowNumber"
                             rows="10"
                             paginator="true"
                             value="#{controlador.autores}"
                             var="registro">
                    <f:facet name="header">
                        <h:outputText value="Autores"/>
                        <p:toolbar>
                            <p:toolbarGroup>
                                <p:commandLink action="#{controlador.adicionar('AUTOR')}"
                                               oncomplete="wdlgDetalhe.show()" 
                                               update=":frmPrincipal:pnlDetalhe">
                                    <p:graphicImage url="./resources/imagens/adicionar.png" 
                                                    style="background-color: transparent; vertical-align: middle; border: none" 
                                                    width="40" height="40"/>
                                </p:commandLink>
                            </p:toolbarGroup>
                        </p:toolbar>
                    </f:facet>
                    <p:column headerText="Nome">
                        #{registro.nome}
                    </p:column>
                    <p:column headerText="Sobrenome">
                        #{registro.sobrenome}
                    </p:column>
                    <p:column width="60">
                        <p:commandLink action="#{controlador.editar(registro)}" 
                                       oncomplete="wdlgDetalhe.show()" 
                                       update=":frmPrincipal:pnlDetalhe">
                            <p:graphicImage url="./resources/imagens/editar.png" 
                                            style="background-color: transparent; vertical-align: middle; border: none" 
                                            width="20" height="20"/>
                        </p:commandLink>
                        <p:spacer width="10"/>
                        <p:commandLink action="#{controlador.excluir(registro)}" 
                                       update=":frmPrincipal:pnlTabela">
                            <p:graphicImage url="./resources/imagens/excluir.png" 
                                            style="background-color: transparent; vertical-align: middle; border: none" 
                                            width="20" height="20"/>
                        </p:commandLink>
                    </p:column>
                </p:dataTable>
            </p:outputPanel>
            <p:dialog id="dlgDetalhe" 
                      widgetVar="wdlgDetalhe" 
                      modal="true" 
                      resizable="false" 
                      closable="true" 
                      header="Detalhes do autor" 
                      styleClass="dialogoSemScroll" >
                <p:panelGrid id="pnlDetalhe" 
                             columns="1" 
                             styleClass="panelGridSemBorda">
                    <p:outputLabel value="Nome:" id="lNome" for="cNome" />
                    <p:inputText id="cNome" value="#{controlador.autor.nome}"/>
                    <p:outputLabel value="Sobrenome:" id="lSobrenome" for="cSobrenome" />
                    <p:inputText id="cSobrenome" value="#{controlador.autor.sobrenome}"/>
                    <f:facet name="footer">
                        <p:toolbar>
                            <p:toolbarGroup>
                                <p:commandLink action="#{controlador.gravar(controlador.autor)}"
                                               oncomplete="wdlgDetalhe.hide()" 
                                               update=":frmPrincipal:pnlTabela">
                                    <p:graphicImage url="./resources/imagens/gravar.png" 
                                                    style="background-color: transparent; vertical-align: middle; border: none" 
                                                    width="40" height="40"/>
                                </p:commandLink>
                            </p:toolbarGroup>
                        </p:toolbar>
                    </f:facet>
                </p:panelGrid>
            </p:dialog>
        </h:form>
    </h:body>
</html>

O managed bean controlador ganhará novos atributos e métodos, como mostrado abaixo:

package controle;

import delegate.FacadeBD;
import entidades.Entidade;
import entidades.Autor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;

@ManagedBean(name = "controlador")
@ViewScoped
public class Controlador implements Serializable {

    @ManagedProperty(value = "#{facadeBD}")
    private FacadeBD facadeBD;
    private Autor autor = new Autor();
    private List<Autor> autores = new ArrayList<Autor>();

    public Controlador() {
    }

    public void adicionar(String entidade) {
        if (entidade.toUpperCase().equals("AUTOR")) {
            autor = new Autor();
        }
    }

    public void editar(Entidade entidade) {
        autor = (Autor) facadeBD.carregar(entidade.getClass(), entidade.getId());
    }

    public void excluir(Entidade entidade) {
        entidade.setFlagRemover(true);
        facadeBD.salvar(entidade);
        autores.clear();
    }

    public void gravar(Entidade entidade) {
        facadeBD.salvar(entidade);
        autores.clear();
    }

    public FacadeBD getFacadeBD() {
        return facadeBD;
    }

    public void setFacadeBD(FacadeBD facadeBD) {
        this.facadeBD = facadeBD;
    }

    public Autor getAutor() {
        return autor;
    }

    public void setAutor(Autor autor) {
        this.autor = autor;
    }

    public List<Autor> getAutores() {
        if (autores.isEmpty()) {
            autores = facadeBD.listar(Autor.class);
        }
        return autores;
    }

    public void setAutores(List<Autor> autores) {
        this.autores = autores;
    }
}

Vamos às linhas mais relevantes do arquivo xhtml, sem entrar no assunto "ciclo de vida do JSF":

  • No cabeçalho da tabela (entre as linhas 28 e 41), inserimos um commandButton que chama o método adicionar do controlador, e no final da requisição, atualiza o componente identificado por :frmPrincipal:pnlDetalhe e executa o método show do componente identificado por wdlgDetalhe.
  • O mesmo acontece entre as linhas 49 e 55, sendo que o método chamado é o editar do controlador.
  • Na linha 57, o método chamado é o excluir.
  • Entre as linhas 66 e 94 está definido o componente wdlgDetalhe, que possui os campos para edição e um commandLink que chama o método gravar do controlador na linha 83 (por enquanto não foi implementado o método cancelar - basta fechar a janela).
  • No começo do arquivo, entre as linhas 11 e 17, uma sobreposição do CSS utilizado pelo Primefaces, para tirar as bordas do p:panelGrid da linha 73 e o scroll do p:dialog da linha 66.

Já no controlador, os métodos são bem tranquilos, ressaltando que para atualizar a tabela, somos obrigados a esvaziar a lista autores (linhas 38 e 43), o que automaticamente causa uma nova leitura dos dados a partir do banco (linhas 63, 64 e 65). Essa implementação funciona, mas é justamente essa nova leitura do banco de dados que vamos evitar, na sequência dessa série de posts. Também há um efeito indesejado que ocorre entre os clientes que estiverem acessando a aplicação: os dados atualizados só aparecem se ocorrer uma operação de CRUD na aplicação, ou seja, após uma edição, ou após excluir um registro. Esse comportamento será substituído pela atualização automática, no próximo post.

Atualizando DataTables dinamicamente (II)

Este post faz parte de uma série que demonstra o uso de Primefaces, Spring, JPA, JSF para criar uma aplicação que atualiza as DataTables dos clientes automaticamente. Confira os outros posts da série aqui: Primefaces

A configuração do Primefaces em um projeto é bem simples, são apenas três etapas:

  1. Bibliotecas: basta acrescentar o arquivo .jar no projeto (no momento, estou usando esse: primefaces-3.6-20130315.085445-6.jar) e baixar um arquivo .jar de tema de sua escolha, deste site: http://repository.primefaces.org/org/primefaces/themes/ (no momento, estou usando o redmond 1.0.10), acrescentando esse arquivo no projeto, também.

  2. Configuração do arquivo web.xml:
        <!-- Primefaces -->
        <context-param>
            <param-name>com.sun.faces.writeStateAtFormEnd</param-name>
            <param-value>false</param-value>
        </context-param>
        <context-param>
            <param-name>com.sun.faces.allowTextChildren</param-name>
            <param-value>true</param-value>
        </context-param>
        <context-param>
            <param-name>primefaces.THEME</param-name>
            <param-value>redmond</param-value>
        </context-param>
        <context-param>
            <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
            <param-value>.xhtml</param-value>
        </context-param>
        <context-param>
            <description>The default for this parameter is false. Fixes IE xhtml content-type restriction.</description>
            <param-name>com.sun.faces.preferXHTML</param-name>
            <param-value>false</param-value>
        </context-param>
        <!-- Fim Primefaces -->

    Uma atenção para a linha 12, que indica qual o nome do tema que será usado.

  3. Configuração nas páginas xhtml: todas as páginas que vão usar componentes do Primefaces deverão ter uma declaração de namespace como destacado abaixo:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:p="http://primefaces.org/ui"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

Pronto. Um exemplo de uma página que usa Primefaces é a página de login que já foi mostrada aqui: Configurando o Spring (II).

Em seguida, vamos demonstrar o uso de p:dataTable em uma página, lembrando que é preciso:


private List<Autor> autores = new ArrayList<Autor>();


    public List<Autor> getAutores() {
        if (autores.isEmpty()) {
            autores = facadeBD.listar(Autor.class);
        }
        return autores;
    }

    public void setAutores(List<Autor> autores) {
        this.autores = autores;
    }

Depois de inserir alguns dados na tabela pessoa, podemos criar uma nova página chamada de autores.xhtml, conforme o código abaixo:

<?xml version='1.0' encoding='UTF-8' ?>
<!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:p="http://primefaces.org/ui"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Livraria</title>
    </h:head>
    <h:body>
        <h:form prependId="false" id="frmPrincipal">
            <p:outputPanel id="pnlTabela">                
                <p:dataTable id="Tabela" 
                             widgetVar="wTabela"
                             rowIndexVar="rowNumber"
                             rows="10"
                             paginator="true"
                             value="#{controlador.autores}"
                             var="registro">
                    <f:facet name="header">
                        <h:outputText value="Autores"/>
                    </f:facet>
                    <p:column headerText="Nome">
                        #{registro.nome}
                    </p:column>
                    <p:column headerText="Sobrenome">
                        #{registro.sobrenome}
                    </p:column>
                </p:dataTable>
            </p:outputPanel>
        </h:form>
    </h:body>
</html>
Apesar de ser um código bem pequeno, há muitas coisas importantes acontecendo aqui:

  1. Primeiro, é bom observar que a lista de autores é carregada do banco de dados apenas uma vez pelo controlador, somente na primeira vez que o getAutores() é executado.
  2. O formulário tem o atributo prependId="false". Isto evita que os ids dos componentes carreguem o sufixo dos componentes-pais, o que em alguns casos vai facilitar para escrever o código.
  3. Dentro do formulário há um outputPanel que envolve a tabela (linha 12). Ele vai ser utilizado para a atualização automática da página.
  4. Os dados da tabela vêm do controlador, pelo atributo autores (linha 18). Cada elemento da lista autores é referenciado na tabela pelo identificador registro (linha 19).
  5. A tabela possui um id e um widgetVar (linhas 13 e 14). Ambos são usados quando vamos executar métodos javaScript relativos ao componente dataTable do Primefaces (widgetVar) ou quando vamos atualizar (Tabela). O importante é que o nome usado não pode ser igual para os dois atributos - eu costumo colocar um "w" como prefixo no widgetVar.
  6. Os atributos rowIndexVar e rows (linhas 14 e 15) sempre devem ser definidos. Há situações em que eles são exigidos (apesar de serem opcionais...).
  7. Há muitas outras propriedades que podem ser (e mais adiante serão) configuradas, mas não vamos discutir isso aqui - a documentação do Primefaces e o showcase deles já demonstra todas elas - vamos citar conforme forem aparecendo.

Bem, esta página já produz resultados, como mostra a figura acima. Para acessá-la enquanto não temos um menu, execute a aplicação e mude o endereço no navegador de index.xhtml para autores.xhtml. No próximo post, vamos mostrar como implementar o CRUD, antes de mostrar a atualização automática nos clientes.

quarta-feira, 2 de outubro de 2013

Atualizando DataTables dinamicamente (I)

Nesta nova série de posts vou mostrar como utilizar JSF, JPA, Spring e Primefaces para criar um mecanismo que atualiza os dados de DataTables de uma mesma aplicação automaticamente, mesmo que estejam executando em máquinas diferentes (ou seja, em sessões de navegadores diferentes). Confira a série toda aqui: Primefaces

Como pré-requisito, toda a estrutura em camadas que foi mostrada na série anterior (vejam neste link: Spring + JPA).

Começaremos com a lista de bibliotecas que eu usei no projeto:

antlr-2.7.7.jar
aopalliance-1.0.jar
asm-attrs.jar
asm.jar
cglib-2.1.3.jar
commons-beanutils-1.8.2.jar
commons-collections-3.2.1.jar
commons-digester-1.7.jar
commons-fileupload-1.2.2.jar
commons-io-2.0.1.jar
commons-javaflow-20060411.jar
commons-lang-2.6.jar
commons-lang3-3.1.jar
commons-logging-1.1.jar
commons-logging.jar
dom4j-1.6.1.jar
groovy-all-2.0.1.jar
hibernate-commons-annotations-4.0.1.Final.jar
hibernate-core-4.1.8.Final.jar
hibernate-entitymanager-4.1.8.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
iText-2.1.7.jar
jasperreports-4.5.1.jar
javassist-3.15.0-GA.jar
javax.faces-2.1.4.jar
jaxb-api-2.1.jar
jaxb-impl-2.1.3.jar
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.0.Final.jar
joda-time-2.1.jar
jstl.jar
jxl-2.6.10.jar
log4j-1.2.16.jar
mysql-connector-java-5.1.6-bin.jar
org.springframework.aop-3.1.2.RELEASE.jar
org.springframework.asm-3.1.2.RELEASE.jar
org.springframework.aspects-3.1.2.RELEASE.jar
org.springframework.beans-3.1.2.RELEASE.jar
org.springframework.context-3.1.2.RELEASE.jar
org.springframework.context.support-3.1.2.RELEASE.jar
org.springframework.core-3.1.2.RELEASE.jar
org.springframework.expression-3.1.2.RELEASE.jar
org.springframework.instrument-3.1.2.RELEASE.jar
org.springframework.instrument.tomcat-3.1.2.RELEASE.jar
org.springframework.jdbc-3.1.2.RELEASE.jar
org.springframework.jms-3.1.2.RELEASE.jar
org.springframework.orm-3.1.2.RELEASE.jar
org.springframework.oxm-3.1.2.RELEASE.jar
org.springframework.test-3.1.2.RELEASE.jar
org.springframework.transaction-3.1.2.RELEASE.jar
org.springframework.web-3.1.2.RELEASE.jar
org.springframework.web.portlet-3.1.2.RELEASE.jar
org.springframework.web.servlet-3.1.2.RELEASE.jar
org.springframework.web.struts-3.1.2.RELEASE.jar
poi-3.7-20101029.jar
postgresql-9.2-1000.jdbc4.jar
primefaces-3.6-20130315.085445-6.jar
primefaces-extensions-0.7.0.jar
redmond-1.0.10.jar
spring-ldap-1.3.1.RELEASE-all.jar
spring-security-3.0.0.RELEASE.zip
spring-security-acl-3.1.2.RELEASE.jar
spring-security-aspects-3.1.2.RELEASE.jar
spring-security-cas-3.1.2.RELEASE.jar
spring-security-config-3.1.2.RELEASE.jar
spring-security-core-3.1.2.RELEASE.jar
spring-security-crypto-3.1.2.RELEASE.jar
spring-security-ldap-3.1.2.RELEASE.jar
spring-security-openid-3.1.2.RELEASE.jar
spring-security-remoting-3.1.2.RELEASE.jar
spring-security-taglibs-3.1.2.RELEASE.jar
spring-security-web-3.1.2.RELEASE.jar
standard.jar
xstream-1.3.1.jar

É claro que nem todas serão necessárias no momento, mas é o que eu uso com mais frequência e vou deixar a lista assim, porque com todos esses arquivos o funcionamento é garantido. Algumas versões já são um pouco antigas, e provavelmente uma migração exija alguns ajustes.

O ambiente é o JBoss as 7.1.1, NetBeans 7.3.1, Java 7, Hibernate 4, JPA 2, MySQL ou PostgreSQL, Primefaces 3.6 ou superior.

Devemos começar configurando o JBoss:
  1. Para instalar a versão 7.1.1, basta fazer o download, descompactar, e copiar os arquivos jar do MySQL e/ou do PostgreSQL para a pasta <pasta_do_JBoss>/standalone/deployments e o próprio servidor se encarregará de instalar os módulos.
  2. Há um script na pasta bin do JBoss chamado add-user.sh (ou add-user.bat, para Windows). É necessário executar esse script e seguir as indicações para cadastrar um usuário e senha de administrador.
  3. Em seguida, instalar o JBoss no NetBeans (a versão 7.1.1 do JBoss só é suportada a partir da versão 7.3.1 do NetBeans).
  4. Depois de iniciar o JBoss pelo NetBeans, é preciso acessar o Console Admin (em serviços - botão direito do mouse sobre o JBoss, escolher Visualizar Console Admin ou entrando pelo endereço localhost:9990, geralmente).
  5. Já na tela de administração,é preciso configurar um datasource:
  • No canto direito superior escolher Profile, em seguida no menu à esquerda escolher Datasources, e na tela central escolher add.
  • Preencher Name e JNDI Name, lembrando que o JNDI Name deve coincidir com o jndi-name usado no jboss-web.xml (no meu caso, java:/fonteDeDados).
  • Na tela seguinte escolher o banco de dados (MySQL, PostgreSQL).
  • Na tela seguinte preencher com os dados da conexão (jdbc:postgresql://meuServidor:5432/meuBancoDeDados, usuário e senha do banco).
  • Quando a configuração voltar para a lista de Datasources, é preciso selecionar o Datasource que acabou de ser instalado e clicar em enable para habilitar. Se acontecer algum erro, é só remover e fazer tudo novamente.
Depois do JBoss configurado, a estrutura mostrada na série de posts anterior vai se conectar ao datasource fonteDeDados, e qualquer alteração de servidor ou tecnologia de banco de dados será feita diretamente no console admin do JBoss. Ou seja: para a aplicação, a configuração da conexão com um banco de dados é totalmente delegada ao datasource.


Neste ponto, já é possível executar o aplicativo, fazer a autenticação e visualizar a página index.xhtml padrão criada pelo NetBeans. É claro que todos os passos da configuração do Spring e Spring Security mostradas anteriormente devem ser seguidos.

No próximo post, a configuração do Primefaces e a criação da primeira página do projeto.

domingo, 29 de setembro de 2013

A camada de visão - o managed bean do JSF

Este post faz parte de uma série sobre Spring, JPA e JSF. Caso algum assunto abordado aqui não esteja claro, consulte este link: Spring + JPA.

Finalizando essa série de posts sobre Spring, JPA e JSF, vou mostrar finalmente como juntar todas as camadas e executar uma pequena aplicação para testar tudo o que foi feito até aqui.

Relembrando que a integração do JSF ocorre sem problemas desde que sejam feitas as alterações no obsoleto arquivo faces-config.xml, como já foi dito aqui: A camada de visão - Business Delegate.

Antes de executar o exemplo, é interessante deixar o arquivo persistence.xml configurado para apagar e criar as entidades, pois assim o teste pode ser refeito quantas vezes forem necessárias.

Vamos usar uma classe Pessoa,  como foi mostrado aqui: Criando entidades de bancos de dados e uma classe Autor conforme o código a seguir:

package entidades;

import java.io.Serializable;
import javax.persistence.Entity;

@Entity
public class Autor extends Pessoa implements Serializable {

    public Autor() {
    }
}

É apenas uma herança direta, já que a classe Pessoa é abstrata e não pode ser instanciada. Em seguida criamos um pacote chamado controle e dentro dele uma classe Controlador.java conforme o código abaixo:

package controle;

import delegate.FacadeBD;
import entidades.Entidade;
import entidades.Autor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;

@ManagedBean(name = "controlador")
@ViewScoped
public class Controlador implements Serializable {

    @ManagedProperty(value = "#{facadeBD}")
    private FacadeBD facadeBD;

    public Controlador() {
    }

    public String listaGravada() {
        String retorno = "";
        List<Autor> la = facadeBD.listar(Autor.class);
        retorno += "\n\nLista gravada:\n\n";
        if (la != null) {
            for (Autor autor : la) {
                retorno += "("+autor.getId()+") "+autor.getNome() + "\n";
            }
        }
        return retorno;
    }

    public String getResultado() {
        String retorno = "";
        Autor autor1 = new Autor();
        autor1.setNome("Stephen King");
        Autor autor2 = new Autor();
        autor2.setNome("Dan Brown");
        List<Entidade> listaAutores = new ArrayList<Entidade>();
        listaAutores.add(autor1);
        listaAutores.add(autor2);
        retorno += "\nInserindo dois autores\n";
        facadeBD.salvarLista(listaAutores);
        retorno += listaGravada();
        autor1.setFlagRemover(Boolean.TRUE);
        facadeBD.salvar(autor1);
        retorno += "\nExcluindo ("+autor1.getId()+") "+autor1.getNome()+"\n";
        retorno += listaGravada();
        Autor autor3 = new Autor();
        autor3.setNome("ray bradbury");
        listaAutores.add(autor3);
        retorno += "\nInserindo novo autor "+autor3.getNome()+"\n";
        facadeBD.salvarLista(listaAutores);
        retorno += listaGravada();
        retorno += "\nAlterando "+autor3.getNome()+"\n";
        autor3.setNome("Ray Bradbury");
        facadeBD.salvarLista(listaAutores);
        retorno += listaGravada();
        return retorno;
    }

    public FacadeBD getFacadeBD() {
        return facadeBD;
    }

    public void setFacadeBD(FacadeBD facadeBD) {
        this.facadeBD = facadeBD;
    }
}

Esta classe possui um atributo facadeBD, injetado pelo JSF, ou seja, é uma instância da classe FacadeBD. Podemos verificar na linha 13 que essa classe é colocada no contexto do JSF com o nome "controlador" (em minúsculas), e é como será referenciada logo a seguir, no página que será construída. Temos ainda um método listaGravada para recuperar o conteúdo do banco de dados correspondente à tabela pessoas. Podemos observar, na linha 25, o uso do método listar.

Finalmente, um método acessor getResultado na linha 35. Apesar de não termos um atributo resultado, verificamos aqui que o JSF só precisa do método acessor, e não do atributo, como já era esperado. Esse método faz várias chamadas aos métodos DAO por meio de facadeBD. É importante ressaltar que não há preocupação com conexões, transações, etc.. Vemos também que para excluir um registro do banco de dados, mudamos o atributo flagRemover para true e salvamos. No final do processamento, temos uma String com o histórico das operações, que vai ser acessada na página a seguir:

<?xml version='1.0' encoding='UTF-8' ?>
<!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://java.sun.com/jsf/html">
    <h:head>
        <title>Livraria</title>
    </h:head>
    <h:body>
        <h:inputTextarea cols="100" rows="50" value="#{controlador.resultado}" />
    </h:body>
</html>

Na linha 9, temos a referência ao "atributo" resultado, acessado por meio do método getResultado do managed bean controlador. Ao executar essa aplicação, há um redirecionamento para a página de autenticação, e em seguida, ocorre o processamento nas camadas e o histórico de operações é exeibido no inputTextArea:

Inserindo dois autores


Lista gravada:

(1) Stephen King
(2) Dan Brown

Excluindo (1) Stephen King


Lista gravada:

(2) Dan Brown

Inserindo novo autor ray bradbury


Lista gravada:

(2) Dan Brown
(3) ray bradbury

Alterando ray bradbury


Lista gravada:

(2) Dan Brown
(3) Ray Bradbury

Outra observação importante é que também não há uma preocupação com as classes que vão ser gravadas. Pode ser uma lista com apenas um tipo de classe (autores) ou com mais de um tipo (autores mesclados com publicações, por exemplo). A única restrição é que as classes devem ser subclasses de Entidade.

Ainda, se observarmos as chamadas que foram feitas ao método salvarLista, veremos que em nenhum momento removemos da lista listaAutores o autor que foi excluído do banco. A classe GenericDAO já prevê tudo isso, justamente para que a camada de visão fique ainda mais desacoplada da camada de serviços, deixando para o desenvolvedor apenas a preocupação com o fluxo da aplicação e a disposição dos dados nas páginas.

E assim termina a série de posts sobre uso de JPA, Spring, JSF, MVC. Temos um projeto básico completo que será usado na próxima série de posts, ode serão abordados os relacionamentos entre entidades e como utilizar o Primefaces para construção de uma aplicação completa.

quinta-feira, 26 de setembro de 2013

A camada de visão - Business Delegate

Este post faz parte de uma série sobre Spring, JPA e JSF. Caso algum assunto abordado aqui não esteja claro, consulte este link: Spring + JPA.

Quando utilizamos Business Delegate (BD), temos como principal objetivo reduzir o acoplamento entre a camada de visão propriamente dita e os serviços. Neste exemplo que venho construindo desde alguns posts anteriores essa abordagem talvez não seja necessária, mas a aplicação já ficará preparada para evoluir, se for necessário.

Vamos criar um pacote chamado delegate e duas classes semelhantes às classes da camada de controle (ou serviços):

package delegate;

import entidades.Entidade;
import java.io.Serializable;
import java.util.List;
import service.AbstractService;

public abstract class AbstractBD<T extends Entidade> {

    protected AbstractService<T> service;

    public abstract void setService(AbstractService<T> service);

    public void salvar(T entidade) {
        service.salvar(entidade);
    }

    public void salvarLista(List<T> lista) {
        service.salvarLista(lista);
    }

    public T carregar(Class classe, Serializable codigo) {
        return service.carregar(classe, codigo);
    }

    public List<T> listar(Class classe) {
        return service.listar(classe);
    }

    public List<T> consultaPersonalizada(String consulta) {
        return service.consultaPersonalizada(consulta);
    }

    public List<T> consultaNativa(String consulta) {
        return service.consultaNativa(consulta);
    }

}

package delegate;

import java.io.Serializable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import service.AbstractService;

@Component(value = "bd")
public class GenericBD extends AbstractBD implements Serializable {

    @Override
    @Value("#{genericService}")
    public void setService(AbstractService service) {
        this.service = service;
    }
}

Não temos muito o que comentar aqui. Como aconteceu com as classes "Service", temos os métodos que serão utilizados, injeção de dependências dos componentes do Spring que serão necessários, e só. O único ponto relevante é o baixo acoplamento. Mudanças podem ocorrer nos serviços sem que a camada de visão sofra qualquer alteração, e vice-versa. Além do fato de que a camada de serviços trabalha com transações e a camada de visão não percebe isso. Esse é mais um motivo para ressaltar a importância da programação voltada a interfaces (ou voltada a classes abstratas).

Com essa tecnologia, podemos ter mais de um tipo de serviço (serviços para bancos de dados e serviços para arquivos xml, por exemplo) controlados por vários business delegates. Isso permitiria mudar a maneira como se faz a persistência dos dados sem modificar nada na camada de visão. Mas é preciso ainda mais um nível de desacoplamento que pode ser obtido implementando um facade (fachada), que será responsável por reduzir a complexidade dos BDs e fornecer uma classe com métodos que a camada de visão pode entender com maior facilidade. Por exemplo, com base em uma escolha do usuário ou por meio de um arquivo de configuração, o facade poderia decidir se o método "salvar" deve instanciar a gravação em arquivos xml ou em bancos de dados, mas a visão continuaria pedindo apenas para "salvar" os dados, sem saber como isto seria feito.

Vou mostrar a seguir o meu modelo de classe facade, que já é utilizado em vários projetos. No mesmo pacote delegate, criamos a classe facadeBD:

package delegate;

import entidades.Entidade;
import java.io.Serializable;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import org.primefaces.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("facadeBD")
@ManagedBean(name = "facadeBD")
@ViewScoped
public class FacadeBD implements Serializable {

    public static final short SALVAR = 1;
    public static final short SALVARLISTA = 2;
    public static final short LISTAR = 3;
    public static final short CARREGAR = 4;
    public static final short CONSULTAPERSONALIZADA = 5;
    public static final short CONSULTANATIVA = 6;
    private Class classe;
    private Serializable chavePrimaria;
    private Entidade objetoRetorno;
    private Entidade entidade;
    private List listaRetorno;
    private String consulta;
    private List listaEntidades;
    @Autowired
    private GenericBD bd;

    public FacadeBD() {
    }

    public void salvar(Entidade entidade) {
        this.entidade = entidade;
        executar(SALVAR);
    }

    public void salvarLista(List listaEntidades) {
        this.listaEntidades = listaEntidades;
        executar(SALVARLISTA);
    }

    public List listar(Class classe) {
        this.classe = classe;
        executar(LISTAR);
        return listaRetorno;
    }

    public Entidade carregar(Class classe, Serializable chavePrimaria) {
        this.classe = classe;
        this.chavePrimaria = chavePrimaria;
        executar(CARREGAR);
        return objetoRetorno;
    }

    public List consultaPersonalizada(String consulta) {
        this.consulta = consulta;
        executar(CONSULTAPERSONALIZADA);
        return listaRetorno;
    }

    public List consultaNativa(String consulta) {
        this.consulta = consulta;
        executar(CONSULTANATIVA);
        return listaRetorno;
    }

    public Boolean mensagemNova(FacesMessage mensagem) {
        Boolean adicionar = true;
        List<FacesMessage> lm = FacesContext.getCurrentInstance().getMessageList();
        if (lm != null && lm.size() > 0) {
            for (FacesMessage fm : lm) {
                if (fm.getDetail().trim().equals(mensagem.getDetail().trim())) {
                    adicionar = false;
                }
            }
        }
        return adicionar;
    }

    public void executar(short metodo) {
        FacesMessage mens;
        String mensagem = "";
        try {
            if (metodo == SALVAR) {
                bd.salvar(entidade);
            }
            if (metodo == SALVARLISTA) {
                bd.salvarLista(listaEntidades);
            }
            if (metodo == LISTAR) {
                listaRetorno = bd.listar(classe);
            }
            if (metodo == CARREGAR) {
                objetoRetorno = bd.carregar(classe, chavePrimaria);
            }
            if (metodo == CONSULTAPERSONALIZADA) {
                listaRetorno = bd.consultaPersonalizada(consulta);
            }
            if (metodo == CONSULTANATIVA) {
                listaRetorno = bd.consultaNativa(consulta);
            }
        } catch (Exception e) {
            System.out.println("===== erro ====");
            if (classe != null) {
                System.out.println("Classe: " + classe.getSimpleName());
            } else {
                System.out.println("Classe: null");
            }
            System.out.println("Método: " + metodo);
            System.out.println("Consulta: " + consulta);
            System.out.println("Chave: " + chavePrimaria);
            e.printStackTrace();
            System.out.println("===============");
            mens = new FacesMessage(FacesMessage.SEVERITY_FATAL, "Erro!", e.getMessage());
            if (mensagemNova(mens)) {
                FacesContext.getCurrentInstance().addMessage(null, mens);
            }
            RequestContext.getCurrentInstance().update("growl");
        } finally {
            if (!mensagem.isEmpty()) {
                mens = new FacesMessage(FacesMessage.SEVERITY_INFO, "Sucesso", mensagem);
                if (mensagemNova(mens)) {
                    FacesContext.getCurrentInstance().addMessage(null, mens);
                }
                RequestContext.getCurrentInstance().update("growl");
            }
        }
    }

    public GenericBD getBd() {
        return bd;
    }

    public void setBd(GenericBD bd) {
        this.bd = bd;
    }

}

  • Como podemos ver na anotação da linha 15, facadeBD é um managed bean, e poderá ser injetado por meio do JSF em outro managed bean com o mesmo escopo (@ViewScoped).
  • facadeBD também é um componente no contexto do Spring, e pode receber uma injeção do GenericBD (linhas 32 e 33). Aqui cabe uma observação: para integrar o Spring com o JSF permitindo injeção de dependências entre os contextos, utilizamos uma pequena configuração no arquivo faces-config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.0"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
    </application>
</faces-config>

  • No trecho compreendido entre as linhas 38 e 71, temos a implementação dos métodos que serão utilizados pela camada de visão. Para salvar uma entidade que foi instanciada no objeto autor, por exemplo, a visão só saberá invocar "salvar(autor)". Todo o processamento de transações, conexão com o banco de dados, decisão sobre inserir ou atualizar, e outros detalhes, serão processados em suas respectivas camadas, de maneira transparente.
  • O método executar que inicia na linha 86, desvia para o business delegate bd que foi injetado pelo Spring, todo o restante do processamento.
O restante do código resume-se aos gets e sets e processamento de mensagens do JSF.

No próximo post, a criação de um controlador do JSF que fará a integração entre as páginas e o facadeBD.

quarta-feira, 25 de setembro de 2013

Transações e a camada de controle

Este post faz parte de uma série sobre Spring, JPA e JSF. Caso algum assunto abordado aqui não esteja claro, consulte este link: Spring + JPA.

Como foi mostrado nos dois posts anteriores, Temos um pacote entidades onde serão descritas as tabelas do banco de dados e um pacote dao, onde estão as classes que fazem o acesso aos dados do banco, por meio de JPA+Hibernate. Esses dois pacotes formam uma camada que frequentemente é chamada de camada de Modelo (o M do MVC).

Manter essa camada separada das demais facilita a mudança de uma tecnologia para outra. Por exemplo, suponhamos que o desenvolvedor precisar mudar de Hibernate para EclipseLink. Somente essa camada precisará ser alterada, além de algumas bibliotecas e configurações do Spring, sem afetar o restante do sistema.

Da mesma maneira, uma camada de Controle (o C do MVC) onde as transações são configuradas, deve ser implementada separadamente das outras camadas, para que se possa jogar para ela toda essa responsabilidade. A camada de controle muitas vezes é citada como camada de serviço, por isso as denominações "service" que acrescentaremos a seguir.

Vamos ao código de uma classe abstrata que vai direcionar como os métodos DAO da camada de modelo devem ser controlados:

package service;

import dao.InterfaceDAO;
import java.io.Serializable;
import java.util.List;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public abstract class AbstractService<T> {

    protected InterfaceDAO<T> dao;

    public void setDao(InterfaceDAO<T> dao) {
        this.dao = dao;
    }

    public void salvar(T entity) {
        dao.salvar(entity);
    }

    public void salvarLista(List<T> lista) {
        dao.salvarLista(lista);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public T carregar(Class classe, Serializable chave) {
        return dao.carregar(classe, chave);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<T> listar(Class classe) {
        return dao.listar(classe);
    }


    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<T> consultaPersonalizada(String consulta) {
        return dao.consultaPersonalizada(consulta);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<T> consultaNativa(String consulta) {
        return dao.consultaNativa(consulta);
    }
}

Foi criado um pacote chamado service e uma classe chamada AbstractService. Nela está centralizado todo o controle das transações que ocorrerão. É óbvio que isso só funciona se todas as operações relativas ao CRUD das entidades passar por essa camada.

Atenção para as linhas 9 e 26. Na linha 9 determinamos que todos os métodos descritos na classe exigirão uma transação, a menos que outra anotação interna determine outra configuração. Já na linha 26, estamos dizendo que o método carregar, em particular, suporta a utilização de uma transação já em andamento, mas não precisa de uma transação para ser utilizado. Não vou entrar em detalhes sobre esse assunto, mas a documentação do Spring explica muito bem essas anotações.

As outras linhas com anotações semelhantes servem para o mesmo propósito. Dessa maneira, nenhuma outra parte do sistema deve abordar controle de transações. Essa classe é a única responsável por essa tarefa, e deve interceptar todas as operações de CRUD.

Em seguida, no mesmo pacote service, criamos uma classe GenericService (agora concreta) para disponibilizar esse serviço, já que a classe abstrata não pode ser instanciada:

package service;

import dao.InterfaceDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service("genericService")
public class GenericService extends AbstractService {

    @Override
    @Value("#{genericDAO}")
    public void setDao(InterfaceDAO dao) {
        this.dao = dao;
    }

}

Esta classe é bastante simples, e tem poucas coisas relevantes para se comentar. Ela utiliza uma injeção de dependência na linha 11, trazendo ao atributo chamado de dao (que foi herdado de AbstractService - linha 12) o objeto genericDAO (identificador reconhecido pelo Spring - confira a linha 11 da classe no post anterior). Podemos notar também que há uma anotação na linha 7, nomeando essa classe no contexto (do mesmo modo que foi feito na classe GenericDAO) para que o Spring possa injetar uma instância dessa classe em outra camada, como veremos no próximo post.

Essa construção possibilita que a camada seguinte dependa apenas da classe GenericService. Qualquer mudança no contexto transacional será feita na classe abstrata, sem atingir o restante das classes da aplicação!

Uma observação a respeito da injeção de dependências: se o atributo dao e o identificador genericDAO tiverem uma correspondência única, ou seja, se houver apenas um objeto no contexto do Spring do tipo AbstractDAO, podemos utilizar uma abordagem com anotação @Autowired, e basta definir um atributo com os mesmos nome e tipo da classe desejada, anotá-lo com @Autowired e declarar seus get e set. Essas variações dependem do objetivo da classe - uma classe que vai manipular vários componentes do contexto do Spring que tenham o mesmo tipo mas nomes de classe diferentes, pode ser implementada para trabalhar strings, obter em tempo de execução o nome da classe e instanciá-la. Nesse caso, utiliza-se @value referenciando uma EL com o nome da classe, como foi feito aqui. Mas se os objetos forem únicos, pode-se utilizar apenas a abordagem com @Autowired. Já foi utilizada uma abordagem dessas quando foi implementada a classe GenericDAO, com o atributo jpaTemplate (Como criar uma camada DAO).

No próximo post, a camada de Visão.

terça-feira, 24 de setembro de 2013

Como criar uma camada DAO

Este post faz parte de uma série sobre Spring, JPA e JSF. Caso algum assunto abordado aqui não esteja claro, consulte este link: Spring + JPA.

Criar uma camada DAO (Data Access Objects) que possa ser reutilizada em outros projetos não é uma tarefa muito difícil, embora envolva vários conceitos de OOP (Object Oriented Programming) e Design Patterns.

Este exemplo mostrará uma construção que vai disponibilizar ao desenvolvedor um ambiente de CRUD (Create, Retrieve, Update, Delete) com algumas características interessantes:

  1. decisão automática por inserção ou atualização, baseada no valor da chave primária;
  2. recuperação por consulta total, personalizada ou nativa do banco de dados;
  3. exclusão direta ou por meio do sinalizador da classe Entidade (flagRemover);
  4. processamento de uma única entidade ou de uma lista de entidades, pertencentes a qualquer subclasse da classe Entidade.
A última característica é a mais interessante, pois possibilita enviar várias entidades diferentes de uma só vez, garantindo o processamento dentro da mesma transação, o que garante um rollback total em caso de erro.

Dentro de um pacote chamado de dao, vamos criar uma interface chamada InterfaceDAO. É muito importante a programação voltada a interfaces (ou classes abstratas, quando for interessante), pois traz ao projeto uma versatilidade muito grande, permitindo que sejam agregados outros mecanismos, sem interferir naquilo que já foi desenvolvido.

Segue o código da classe InterfaceDAO:

package dao;

import java.io.Serializable;
import java.util.List;

public interface InterfaceDAO<T> {

    public void salvar(T entidade);

    public void salvarLista(List<T> lista);

    public T carregar(Class<T> classe, Serializable chavePrimaria);

    public List<T> listar(Class<T> classe);

    public List<T> consultaPersonalizada(String consulta);

    public List<T> consultaNativa(String consulta);

}

Essa interface orienta o desenvolvedor a respeito dos métodos que serão necessários ao projeto e que podem ser utilizados em outras camadas, já que são métodos públicos. Não há método de atualização e exclusão na interface, pois a atualização depende apenas do valor da chave primária, e a exclusão, como foi visto anteriormente, será feita por meio da sinalização do atributo flagRemover. É importante observar que a interface não fixa o tipo das classes que serão processadas. Em vez disso, usa o tipo genérico, representado por T.

A seguir, a implementação da classe  que o projeto utilizará para as operações CRUD:

package dao;

import entidades.Entidade;
import java.io.Serializable;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.orm.jpa.support.SharedEntityManagerBean;
import org.springframework.stereotype.Repository;

@Repository("genericDAO")
@Scope(value = "singleton")
public class GenericDAO<T extends Entidade> implements InterfaceDAO<T> {

    @Autowired
    private SharedEntityManagerBean jpaTemplate;

    @Override
    public void salvar(T entidade) {
        if (entidade != null) {
            salvarSemFlush(entidade);
            jpaTemplate.getObject().flush();
        }
    }

    private void salvarSemFlush(T entidade) {
        if (entidade != null) {
            try {
                if (entidade.getFlagRemover()) {
                    if (entidade.getId() != null) {
                        if (jpaTemplate.getObject().find(entidade.getClass(), entidade.getId()) != null) {
                            jpaTemplate.getObject().remove(jpaTemplate.getObject().getReference(entidade.getClass(), entidade.getId()));
                        }
                    }
                } else {
                    if (entidade.getId() == null) {
                        jpaTemplate.getObject().persist(entidade);
                    } else {
                        if (jpaTemplate.getObject().find(entidade.getClass(), entidade.getId()) != null) {
                            jpaTemplate.getObject().merge(entidade);
                        }
                    }
                }
            } catch (Exception e) {
                System.out.println(e.getLocalizedMessage());;
            }
        }
    }

    @Override
    public void salvarLista(List<T> lista) {
        Integer i = 0;
        if (lista != null) {
            for (T entidade : lista) {
                salvarSemFlush(entidade);
                i++;
                // sincroniza a cada 500 registros
                if ((i % 500) == 0) {
                    try {
                        jpaTemplate.getObject().flush();
                    } catch (Exception e) {
                        System.out.println(e.getLocalizedMessage());;
                    }
                }
            }
            // sincroniza os ultimos registros
            if (i % 500 != 0) {
                try {
                    jpaTemplate.getObject().flush();
                } catch (Exception e) {
                    System.out.println(e.getLocalizedMessage());;
                }
            }
        }
    }

    @Override
    public T carregar(Class<T> classe, Serializable chave) {
        if (classe == null || chave == null) {
            return null;
        }
        return jpaTemplate.getObject().find(classe, chave);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> listar(Class<T> classe) {
        if (classe == null) {
            return null;
        }
        return jpaTemplate.getObject().createQuery("select a from " + classe.getSimpleName() + " a ").getResultList();
    }

    @Override
    public List<T> consultaPersonalizada(String consulta) {
        if (consulta == null) {
            return null;
        }
        return jpaTemplate.getObject().createQuery(consulta).getResultList();
    }

    @Override
    public List<T> consultaNativa(String consulta) {
        if (consulta == null) {
            return null;
        }
        return jpaTemplate.getObject().createNativeQuery(consulta).getResultList();
    }

    public SharedEntityManagerBean getJpaTemplate() {
        return jpaTemplate;
    }
}

Vamos à análise das linhas mais relevantes:

  • linhas 11 e 12: configurações do Spring. Essa classe será injetada pelo Spring posteriormente, na camada de controle.
  • linha 13:Essa classe restringe o uso às subclasses de Entidade e utiliza os métodos definidos em InterfaceDAO.
  • linhas 15 e 16: modelo JPA injetado pelo Spring, e que foi definido em AppConfig (Configurando o Spring (II)).
  • linhas 19 e 26: aqui a camada DAO possibilita ao desenvolvedor chamar o método salvar (para apenas uma entidade, com flush no final) ou chamar o método salvarLista (linha 51), que vai gerenciar o flush conforme o processamento vai atingindo blocos de 500 registros.
  • demais linhas: implementação dos demais métodos.

No próximo post, a implementação de um serviço que faz o gerenciamento de transações para chamar os métodos DAO.

segunda-feira, 23 de setembro de 2013

Criando entidades de bancos de dados

Este post faz parte de uma série sobre Spring, JPA e JSF. Caso algum assunto abordado aqui não esteja claro, consulte este link: Spring + JPA.

Uma boa alternativa para quem está desenvolvendo um projeto e precisa agilizar os testes durante a modelagem dos dados e a criação da camada DAO é deixar a unidade de persistência e o Hibernate se encarregarem da criação das tabelas das entidades no banco de dados.

Para que isso ocorra, é necessária uma propriedade no arquivo persistence.xml, mais precisamente na linha 13, mostrada abaixo:


  
    org.hibernate.ejb.HibernatePersistence
    java:/fonteDeDados
    entidades.Pessoa
    false
    
      
      
      
      
      
      
    
  


Essa propriedade indica ao Hibernate para apagar todas as tabelas e recriá-las, a cada vez que a aplicação for executada. Depois que todos os testes terminarem, a propriedade pode ser deixada com o valor update, que atualiza ou cria automaticamente qualquer atributo novo ou tabela que for acrescentada ao projeto.


  
    org.hibernate.ejb.HibernatePersistence
    java:/fonteDeDados
    entidades.Pessoa
    false
    
      
      
      
      
      
      
    
  


Caso não haja mais necessidade de alterar a estrutura do banco de dados, a propriedade pode ser simplesmente retirada do arquivo.

Uma pequena observação: se a propriedade estiver configurada para create-drop e ocorrer erro no servidor de aplicações, é preciso remover manualmente as tabelas do banco de dados.

Para demonstrar estas configurações, vou criar uma série de posts com a construção das camadas MVC que serão usadas em um projeto simples para uma livraria.

O primeiro passo é preparar uma classe abstrata que servirá de modelo para todas as entidades que forem criadas no projeto. O objetivo é que todas as entidades atendam os requisitos abaixo:

  1. um método chamado getId() que retorne o valor de sua chave primária;
  2. um atributo temporário chamado flagRemover, que sinalizará ao DAO que o objeto que chegou até ele deve ser removido do banco de dados;

A importância desta classe será demonstrada quando for implementado o CRUD do projeto. Vamos ao código:

package entidades;

import java.io.Serializable;
import javax.persistence.Transient;

public abstract class Entidade implements Serializable {

    @Transient
    public boolean flagRemover;

    public abstract Serializable getId();

    public Boolean getFlagRemover() {
        return flagRemover;
    }

    public void setFlagRemover(Boolean flagRemover) {
        this.flagRemover = flagRemover;
    }
}

Numa rápida análise, o pacote entidades conterá essa classe abstrata que tem  um atributo flagRemover, com seus métodos de acesso (get e set) e um método público abstrato que simula o método get de um atributo chamado id. Esse método possibilita generalizar a obtenção da chave primária de qualquer classe de entidade que herde essa classe, já que ele será implementado na subclasse, e poderá retornar justamente o atributo da chave primária da subclasse. Observamos também que o atributo flagRemover não será escrito no banco de dados, pois está anotado com @Transient. Ele será utilizado apenas para marcar uma entidade que deve ser removida, antes de enviá-la ao DAO.

É bastante clara a vantagem de usar uma classe abstrata em vez de uma interface, pois podemos deixar alguns métodos prontos já para a subclasse, o que não poderia ser feito com o uso de uma interface.

Um rápido exemplo:

package entidades;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public abstract class Pessoa extends Entidade implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer reg;
    @Column(length=50)
    private String nome;
    @Column(length=50)
    private String sobrenome;

    public Integer getReg() {
        return reg;
    }

    public void setReg(Integer reg) {
        this.reg = reg;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getSobrenome() {
        return sobrenome;
    }

    public void setSobrenome(String sobrenome) {
        this.sobrenome = sobrenome;
    }

    @Override
    public Serializable getId() {
        return reg;
    }

}

A classe Pessoa (que também é uma classe abstrata!) herda a estrutura da classe Entidade (linha 11), ou seja, automaticamente possui um atributo flagRemover com get e set. Essa classe possui sua própria chave primária reg, anotada devidamente com @Id, e que não tem, inicialmente, uma ligação com o método getId.  No final do código, podemos ver a implementação (que é obrigatória) do método getId, que está retornando o atributo reg (chave primária da classe Pessoa). Portanto, outras classes poderão acessar o valor da chave primária da classe Pessoa, utilizando o método getId(), sem saber que o atributo original é chamado de reg, ou seja, deixando o atributo reg encapsulado.

No próximo post, a construção das classes DAO.