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:



Nenhum comentário:

Postar um comentário