JSF - Tratando exceção de negócio
Autor: Marcos Oliveira | Publicado: 2011-10-22T11:01:00-07:00

Introdução ao ProblemaExistem ocasiões que temos que fazer validações de entidades, e no caso de inconsistências nas entradas informados pelo usuário, interromper o fluxo e mostrar uma mensagem alertando sobre o erro. É o caso de o usuário tentar inserir um produto com o mesmo nome de outro já cadastrado, e isso não esteja de acordo com as regras de negócio. Em geral, a solução na camada de serviço para esse tipo de situação é lançar Exceções de Negócio na camada de serviço. Em aplicações MVC utilizando JSF, na classe controladora (BackingBean) geralmente temos que tratar exceções de negócio ocasionalmente lançadas pelo Serviço. Ex:  ..BackingBean......public String inserir(){ //método de ação do backing bean, acionado na fase invoke application do ciclo de vida JSF   try{    this.servico.inserir(entidade); // chamada ao método de serviço    return "sucesso"; // "sucesso" é a página de retorno no caso de sucesso   }catch(NegocioException e){ // Considere que NegocioException é apenas uma RuntimeException utilizada pela camada de serviço para lançar exceções de negócio    String msgFormatada = getMensagem(e.getMessage()); // A mensagem de erro é informada ao lançar o NegocioException, considere que o getMensagem é um método que obtém uma mensagem de um arquivo de propriedades a partir de uma chave    FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_WARN, msgFormatada, msgFormatada);    FacesContext.getCurrentInstance().addMessage(null, facesMessage);    return null;   }  }.....BackingBean..   Mas esse tratamento, muito rotineiro e trivial, deve ser feito para todo método do BackingBean. Deveria ter uma forma de automatizar isso e deixar o código acima bem mais simples e legível.  No Struts podemos implementar org.apache.struts.action.ExceptionHandler e interceptar as exceções fazendo o tratamento. SoluçãoEis aqui a solução!!!  Você não percebe mas o JSF encapsula o servlet e através das fases do ciclo de vida, executa uma série de tarefas antes de evocar seu método deixando seu backing bean naquele estado perfeito, com todos campos carregados, convertidos e validados. A solução está exatamente em incluir o tratamento nesse momento em que o JSF evoca seu método.  Para isso precisamos fazer 2 coisas, implementar ActionListener e declarar que o ActionListener utilizado pela aplicação será o que você definiu. 1. Implementar ActionListener: /**  * NegocioExceptionListener  *   * Descrição: Listener que processa eventos da fase do ciclo de vida que invoca  * os métodos de ação, geralmente Invoke Application. Sobrescreve o método  * {@link ActionListenerImpl#processAction(ActionEvent)} apenas para fazer o  * tratamento de exceções de negócio.  *   *   * @see ActionListener  * @see ActionListenerImpl  * @author marcos.oliveira  * @version 1.0  */ public class NegocioExceptionListener extends ActionListenerImpl implements   ActionListener {   /**   * Tenta executar seguir o fluxo normal, chamando o método da superclasse e   * caso ocorra uma exceção de Negócio, é adicionada uma mensagem de alerta e   * a fase Render Response é acionada.   *    * @param e   *            Utilizado na superclasse.   * @see com.sun.faces.application.ActionListenerImpl#processAction(javax.faces.event.ActionEvent)   */  @Override  public void processAction(ActionEvent arg0) {   try {    super.processAction(arg0);   } catch (Exception e) { // Necessário utilizar Exception pois sua    // exceção pode ter sido encapsulada em outras    Throwable exception = e;    do {     if (exception instanceof NegocioException) {      String msgFormatada = getMensagem(e.getMessage());      /*       * A mensagem de erro é informada ao lançar o       * NegocioException, considere que o getMensagem é um método       * que obtém uma mensagem de um arquivo de propriedades a       * partir de uma chave       */       FacesMessage facesMessage = new FacesMessage(        FacesMessage.SEVERITY_WARN, msgFormatada,        msgFormatada);      FacesContext.getCurrentInstance().addMessage(null,        facesMessage);      FacesContext facesContext = FacesContext        .getCurrentInstance();      Application application = facesContext.getApplication();      NavigationHandler navigationHandler = application        .getNavigationHandler();      navigationHandler        .handleNavigation(facesContext, null, null);      facesContext.renderResponse();      break;     }     exception = exception.getCause();    } while (exception != null);    if (exception == null)     throw e;// Seguindo o fluxo normal, caso a exceção ocorrida não    // seja NegocioException   }  }  }2. Incluir no faces.config a declaração seguinte publicando seu ActionListener: "&lt;"action-listener"&gt;"NegocioExceptionListener"&lt;"/action-listener"&gt;" "&lt;"-- Aqui deve ter o nome qualificado '&lt;'pacote'&gt;'.NegocioExceptionListener, e não apenas o nome da classe conforme mostrado  Agora seu Backing Bean pode ficar assim:    Ex:  ..BackingBean...  ...public String inserir() { // método de ação do backing bean, acionado na         // fase invoke application do ciclo de vida JSF   this.servico.inserir(entidade); // chamada ao método de serviço   return "sucesso"; // "sucesso" é a página de retorno no caso de        // sucesso  }.....Novo código BackingBean..  Bem mais simples não? E o Mais Fantástico, pra todo e qualquer método de qualquer BackingBean da aplicação.  Referências:  http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.93.5172&amp;rep=rep1&amp;type=pdf