Neste artigo focaremos na construção de um ManagedBean genérico o bastante para realizar operações de CRUD, independente da regra de negócio que estamos trabalhando. Trabalharemos utilizando JSF e Primefaces, mas nada impede que você adapte o ManagedBean aqui descrito para a sua realidade.

Essa tarefa não é trivial, pois precisamos primeiramente abstrair qualquer tipo de regra de negócio de nossa mente e trabalhar de uma maneira genérica, de forma que qualquer um possa estender nossa classe e usar todos os recursos dela sem problemas de compatibilidade. Sabemos que se perde grande tempo criando ManagedBeans's específicos de CRUD para cada módulo, ou seja, se o seu sistema possui CRUD de Funcionários, Usuários, Clientes e assim por diante, normalmente você criaria um ManagedBean para controlar cada uma desses módulos. Esse trabalho demanda tempo, é repetitivo e propenso a diversos erros.

Nosso objeto de estudo é construir uma classe abstrata que possa ser estendida por qualquer módulo de CRUD, evitando a reescrita de código desnecessário. Mas como dissemos isso não é uma tarefa fácil, mas muito necessária para aumentar a produtividade do seu projeto.

Definindo um Bean Genérico

Antes de qualquer coisa é necessário começar definindo um Bean Genérico que servirá de base para qualquer outro Bean, ou seja, neste Bean teremos método simples como getId(), setId(), hash e equals. Significa que todos os nossos beans deverão estender deste “Bean Genérico”, assim a qualquer hora você poderá fazer: “Funcionario.getId(), Cliente.getId() e assim por diante”, tendo a certeza de que aquele método existe no bean.Veja a Listagem 1.

Listagem 1. Definindo o Bean Genérico


  import java.io.Serializable;
   
  import javax.persistence.GeneratedValue;
  import javax.persistence.Id;
  import javax.persistence.MappedSuperclass;
   
  @MappedSuperclass
  public abstract class AbstractBean implements Serializable {
   
         @Id
         @GeneratedValue(strategy = 
           javax.persistence.GenerationType.IDENTITY)
         private Integer id;
   
         public Integer getId() {
               return id;
         }
   
         public void setId(Integer id) {
               this.id = id;
         }
   
         @Override
         public int hashCode() {
               final int prime = 31;
               int result = 1;
               result = prime * result + ((id == null) 
                 ? 0 : id.hashCode());
               return result;
         }
   
         @Override
         public boolean equals(Object obj) {
               if (this == obj)
                      return true;
               if (obj == null)
                      return false;
               if (getClass() != obj.getClass())
                      return false;
               
               return (obj instanceof AbstractBean) ? 
                (this.getId() == null ?
                 this == obj : this.getId().equals
                  (((AbstractBean)obj).getId())):false;
               
         }
   }

Temos então nosso Bean chamado “AbstractBean” e agora todo o restante irá estender dele, como no exemplo da Listagem 2.

Listagem 2. Bean Funcionario


  public class Funcionario extends AbstractBean {
   }

Criando os ManagedBean's

Temos que pensar que mesmo que nosso ManagedBean abstrato seja muito bom e poderoso, podem ainda haver casos em que é melhor optar por criar um do zero do que estender o nosso ManagedBean abstrato e ficar adaptando até dar certo, casos em que a regra de negócio exige muitos detalhes além da compreensão do nosso código abstrato.

Para isso, criaremos o mais simples dos ManagedBeans, um bem básico que teremos a certeza de que qualquer um poderá estendê-lo, independente da sua regra de negócio. Veja na Listagem 3.

Listagem 3. Criando ManagedBean simples


  public interface BasicMB {
   
         public abstract void addErrorMessage(String s, String s1);
   
         public abstract void addErrorMessage(String s);
   
         public abstract void addInfoMessage(String s, String s1);
   
         public abstract void addInfoMessage(String s);
  }
   
  import java.io.Serializable;
  import javax.faces.application.FacesMessage;
  import javax.faces.context.FacesContext;
   
  public class BasicMBImpl implements BasicMB, Serializable {
   
         public void addErrorMessage(String componentId, 
           String errorMessage) {
               addMessage(componentId, errorMessage, 
                FacesMessage.SEVERITY_ERROR);
         }
   
         public void addErrorMessage(String errorMessage) {
               addErrorMessage(null, errorMessage);
         }
   
         public void addInfoMessage(String componentId, 
          String infoMessage) {
               addMessage(componentId, infoMessage, 
               FacesMessage.SEVERITY_INFO);
         }
   
         public void addInfoMessage(String infoMessage) {
               addInfoMessage(null, infoMessage);
         }
   
         private void addMessage(String componentId, 
          String errorMessage,
                      FacesMessage.Severity severity) {
               FacesMessage message = new FacesMessage
               (errorMessage);
               message.setSeverity(severity);
               FacesContext.getCurrentInstance().addMessage
               (componentId, message);
         }
  }

Então o que temos acima é uma ManagedBean que implementa os métodos mais comuns que são utilizados apenas para mostrar mensagens na tela do usuário. Sabemos que estes métodos podem ser utilizados por qualquer regra de negócio e mesmo que não precisem, eles estarão lá para você utilizá-los quando precisar, mas esse é um caso a parte. Nosso foco é criar um ManagedBean abstrato e completo o suficiente para realizar CRUD's.

A forma mais fácil de explicar a construção de um ManagedBean deste porte é mostrando todo o código e comentando linha à linha, tentando explicar de forma fácil a utilidade de cada método. Na Listagem 4 você verá toda nossa classe já pronta e comentada linha à linha para que você possa entender como a mesma foi construída.

Listagem 4. ManagedBean abstrato completo


  /**
   * *****************************************
   * NOSSOS IMPORTS
   * *****************************************
   * */
  import java.lang.reflect.ParameterizedType;
  import java.util.List;
   
  import javax.faces.application.FacesMessage;
  import javax.faces.context.FacesContext;
  import javax.faces.event.ActionEvent;
   
  import org.primefaces.context.RequestContext;
   
  import br.com.meuprojeto.bean.AbstractBean;
  import br.com.meuprojeto.bo.BasicBO;
  import br.com.meuprojeto.exception.BOException;
  /**
   * *****************************************
   * *****************************************
   * *****************************************
   * */
   
  /*
   * Passamos através de um Generics "Bean" o tipo da nossa Classe,
   * mas sabemos e devemos ter a certeza que ela sempre estende de 
   * AbstractBean. Dessa forma, independente do tipo da nossa 
   * classe podemos usar métodos como
   * equals, hashCode, getId() e setId().
   * */
  public abstract class BasicCrudMBImpl<Bean> 
   extends BasicMBImpl {
   
         /*
          * Definimos aqui como protected para que a classe 
          * que estará estendendo
          * nosso BasicCrudMBImpl possa acessar diretamente 
          * nosso bean. Esse objeto bean servirá para todo 
          * tipo de manipulação do usuário, ou seja,
          * quando ele for inserir um novo Funcionario é 
          * através do objeto "bean", quando
          * ele for alterar um Funcionario também é através do 
          * objeto "bean" e o mesmo acontece com
          * o método de remoção.
          * */
         protected Bean bean;
         
         
         /*
          * A maioria, se não todos os CRUD's, contam com uma lista 
          * de objetos retornados do banco, por exemplo:
          * Lista de Clientes, lista de processos e assim por diante. 
          * Armazenaremos nossa lista no objeto "beans"
          * */
         protected List<Bean> beans;
         
         /*
          * Quando estamos trabalhando com JSF, temos que atualizar
          * os componentes necessários após uma
          * inserção, deleção ou alteração no banco, então temos que 
          * parametrizar os componentes que deverão
          * ser atualizados após essas operações. Para isso recebemos
          * uma lista com os id's dos componentes
          * que devem ser atualizados após determinadas operações.
          *
          *  Ex: Ao clicar em salvar no formulário de Endereço você 
          * quer que este endereço apareça em um
          *  "<p:dataTable>" para o usuário, então você passa 
          * ID deste componente dataTable.
          * */
         private List<String> componentesUpdateOnSave;
   
         
         /*
          * Como o próprio nome já propõe, este método é responsável 
          * por preparar a inserção de um novo
          * objeto. Mas atenção: este irá apenas criar o objeto em 
          * memória, para que você possa manipulá-lo, e não
          * salvá-lo no banco.
          * */
         public void novo(ActionEvent event) {
               
               beforeNew(event);
               
               //Cria o novo objeto de acordo com o tipo 
               // "Bean" passado pelo Generics
               bean = getNewInstanceOfBean();
               
               /*
                * Recebemos através de um <f:attribute> 
                 * a lista de componentes que devem ser atualizados
                * quando toda a operação terminar.
                * */
               componentesUpdateOnSave = (List<String>)
                event.getComponent()
                             .getAttributes().get
                             ("componentesParaAtualizar");
               
               afterNew(event);
         }
   
         /*
          * Nosso método alterar não tem nada de mais, 
          * apenas recebe também uma lista de componentes
          * que devem ser atualizados
          * */
         public void alterar(ActionEvent event) {
               
               beforeEdit(event);
               
               componentesUpdateOnSave = (List<String>) 
               event.getComponent()
                .getAttributes().get
                ("componentesParaAtualizar");
               
               afterEdit(event);
         }
   
         /*
          * Este é o método responsável por retornar uma 
          * instância do nosso "Bean"
          * extraindo dele o tipo que foi passado, ou seja,
          * se seu "Bean" na verdade é um
          * "Cliente", ele criará um objeto do tipo Cliente;
          * */
         private Bean getNewInstanceOfBean() {
               try {
                 ParameterizedType superClass = 
                 (ParameterizedType) getClass()
                 .getGenericSuperclass();
                 Class<Bean> type = 
                 (Class<Bean>) superClass
                  .getActualTypeArguments()[0];
                      return type.newInstance();
               } catch (InstantiationException e) {
                      e.printStackTrace();
                      return null;
               } catch (IllegalAccessException e) {
                      e.printStackTrace();
                      return null;
               }
         }
   
         /*
          * Deleta nosso bean do banco de dados. Não usamos aqui a
          * lista de componentes que devem
          * ser atualizados, pois a operação de deleção é feita 
          * geralmente em uma <p:dataTable> e
          * na própria deleção você já faz o update dos componentes. 
          * Totalmente diferente de operações como
          * alterar e inserir, que dependem da abertura de outra janela, 
          * independente da janela que estamos
          * trabalhando atualmente.
          * Pense da seguinte forma: Você pode criar um formulário 
          * de inserção de clientes e querer chamá-lo através
          * de outra tela que não tem nada haver com o contexto atual, 
          * assim você precisa abstrair o componente que
          * será atualizado após a inserção do novo cliente.
          * */
         public void deletar(ActionEvent event) {
               try {
                      
                      beforeRemove(event);
   
                      getBoPadrao().delete((AbstractBean) bean);
                      addInfoMessage("Registro deletado com sucesso");
   
                      bean = null;
                      
                      afterRemove(event);
   
               } catch (BOException e) {
                      addErrorMessage(e.getMessage());
                      FacesContext.getCurrentInstance()
                      .validationFailed();
               } catch (Exception e) {
                      FacesContext.getCurrentInstance()
                      .validationFailed();
                      addErrorMessage("Erro ao deletar. " 
                      + e.getMessage());
               }
         }
   
         /*
          * Salva o bean no banco. Este método serve tanto para 
          * atualizar quanto para inserir um novo,
          * verificando apenas se o mesmo tem ou não ID associado a ele.
          * */
         public void salvar(ActionEvent event) {
               try {
                      
                 beforeSave(event);
                 if (((AbstractBean) bean).getId() == null) {
                   bean = (Bean) getBoPadrao().save((AbstractBean) bean);
                   addInfoMessage("Registro salvo com sucesso");
   
                 } else {
                   getBoPadrao().update((AbstractBean) bean);
                   addInfoMessage("Registro atualizado com sucesso");
                 }
   
                  bean = null;
   
                 atualizarComponentes(componentesUpdateOnSave);
                   
                 afterSave(event);
                } catch (BOException e) {
                      FacesContext.getCurrentInstance()
                       .validationFailed();
                      addErrorMessage(e.getMessage());
               } catch (Exception e) {
                      FacesContext.getCurrentInstance()
                       .validationFailed();
                      addErrorMessage("Erro ao salvar. " 
                        + e.getMessage());
               }
         }
   
         /*
          * Método responsável por atualizar nossos componentes
          * */
         private void atualizarComponentes(List<String> componentes) {
               for (String compId : componentes) {
                      RequestContext.getCurrentInstance().update(compId);
               }
         }
         
         
         /*
          * Esse ponto é muito importante para abstração da 
          * nossa classe, pois com os métodos abaixo garantimos
          * que outros códigos, com lógicas totalmente diferentes 
          * possam ser adicionadas a nossa classe sem
          * que cause nenhum problema.
          * */
         public abstract void beforeNew(ActionEvent event);
         public abstract void afterNew(ActionEvent event);
         public abstract void beforeEdit(ActionEvent event);
         public abstract void afterEdit(ActionEvent event);
         public abstract void beforeRemove(ActionEvent event);
         public abstract void afterRemove(ActionEvent event);
         public abstract void beforeSave(ActionEvent event);
         public abstract void afterSave(ActionEvent event);
         
               
         
   
         /*
          * Como não sabemos se será carregada a lista de beans do banco, 
          * pois podem ser utilizadas diversas querys para tal tarefa, 
          * obrigamos a implementação deste
          * método, pois precisaremos dessa lista em determinados momentos.
          * */
         public abstract List<Bean> retornaBeansDoBanco();   
   
         
         /*
          * Utilizado para inicializar listas e componentes necessários. 
          * Geralmente é utilizado juntamente com o @PostConstruct.
          * */
         public abstract void init();
         
         /*
          * Getters e Setters
          * */
   
         public Bean getBean() {
               return bean;
         }
   
         public void setBean(Bean bean) {
               this.bean = bean;
         }
   
         public List<Bean> getBeans() {
               try {
                      beans = (List<Bean>) retornaBeansDoBanco();
                      return beans;
               } catch (BOException e) {
                      addErrorMessage(e.getMessage());
               }
               return null;
         }
   
         public void setBeans(List<Bean> beans) {
               this.beans = beans;
         }
   
         /*
          * Precisamos de um BO para realizar as operações de CRUD 
          * juntamente com um DAO, na verdade
          * o DAO é transparente ao ManagedBean, ele nem deve saber 
          * que este existe. O managedBean deve se
          * comunicar com nosso BO. Como não sabemos qual BO 
          * será utilizado, então obrigamos também a
          * implementação desses métodos.
          * */
         public abstract BasicBO getBoPadrao();
   
         public abstract void setBoPadrao(BasicBO boPadrao);
  }

Com isso, o nosso artigo teve como principal objeto demonstrar a construção detalhada de um ManagedBean abstrato o suficiente para quase todos os tipos de CRUD.

Até a próxima!!