Manejo de Excepciones

Introducción

Esta sección tiene por objetivo definir unos lineamientos básicos para el correcto manejo de excepciones dentro de una aplicación jee5 utilizando SEAM.
El correcto manejo de excepciones tiene las siguientes ventajas:

  • Reducir la complejidad para la generación de mensajes de usuario final.
  • Internacionalizar los mensajes de usuario final.
  • Definición de un mecanismo genérico de excepciones que soporte Formateo de mensajes de usuario final para distintos tipos de clientes (web, standalone, webservice, etc).
  • Acople con la consola de excepciones del Framework CORE5.
  • Soporte de múltiples mensajes de usuario ante una misma excepción.

Java tiene la siguiente jerarquía de excepciones

ExceptionHierarchy.png
Imagen tomada de: ”The exception hierarchy in Java“ http://www.javamex.com/tutorials/exceptions/exceptions_hierarchy.shtml
  • Throwable: Cualquier clase que hereda de Throwable se puede lanzar con la instrucción throw
  • Error: Subclases de Error representan errores graves para los cuales el sistema no se puede recuperar. Por ejemplo: OutOfMemoryError
  • Exception: Subclases de Exception representan errores para los cuales el sistema puede realizar algun tipo de recuperación para seguir funcionando, exceptuando las de tipo RuntimeException.
  • RuntimeException: Subclases de RuntimeException representan errores que normalmente no deben ocurrir, pero que potencialmente ocurren. Generalmente estos errores son provocados por errores de programación o mala logica en la programación, por ejemplo el NullPointerException.

Excepciones de tipo Error y RuntimeException son unchecked. Esto quiere decir que los métodos no requieren declarar explícitamente estas excepciones para ser lanzadas.

Sin embargo, a pesar de que el desarrollador no debe crear ni lanzar excepciones unchecked, en algunos casos es necesario capturar excepciones hijas de RuntimeException, como por ejemplo la excepción NumberFormatException, la cual ocurre cuando se intenta convertir un string que no representa un número a un valor numérico.

Uso de Excepciones

  • No se deben crear ni lanzar excepciones de tipo Error o RuntimeException. Estas están destinadas para ser lanzadas por la maquina virtual de Java.
  • No se debe dejar de procesar las excepciones, y se prohíbe el uso de System.out o exception.printStackTrace().

Cuando se genere una excepción en cualquier capa existen las siguientes posibilidades:

  • Propagar la excepción original: Esta tarea no se puede realizar en la capa de EJB, ya que la excepción se propagaría a la capa de presentación sin ningun control. La propagación puede ser de la siguiente forma:
try {

} catch (CoreException e) {
   log.error("Error en acción XXX " + e.getMessage(), e);
   throw e;
}
  • Propagar excepción enmascarada: . La propagación puede ser de la siguiente forma:
try {

} catch (CoreException e) {
   ApplicationException ae = new ApplicationException("error2", String[]{"param1"}, e);
   log.error("Error en acción XXX " + ae.getMessage(), ae);
   throw ae;
}
  • Capturar la excepción: Esta tarea debe ser realizada en la capa de EJB ya que sus métodos deben tener la anotación @Interceptors({ExceptionInterceptor.class}) para capturar las Excepciones que son propagadas en las otras capas o en ella misma.

Interceptor de Excepciones

Este interceptor es parte del framework Core5 y se encarga de capturar excepciones de tipo CoreException o Exception. Cuando captura las excepciones el interceptor registra dicha excepción en la base de datos, en la tabla CORE_LOG. Los datos que guarda en la tabla son: la clase origen de la excepción, el método origen de la excepción, el mensaje de la excepción, la fecha, el usuario del sistema, y la traza de la excepción.

Cuando es una excepción de tipo CoreException, el interceptor obtiene el mensaje de error que se debe mostrar al usuario y lo agrega al componente facesMessages. De esta forma el mensaje de error es desplegado en la página.

Core5 también cuenta con una consola que permite consultar los datos de las excepciones registradas con el interceptor. Para acoplar esta consola al proyecto debe seguir los pasos de la guía [EXCEPTION-CONSOLE]

Excepción CoreException

El framework Core5 provee la excepción CoreException, la cual tiene la funcionalidad de manejar mensajes de error a partir de los archivos de recursos (internacionalización). Esta excepcion tiene los siguientes constructores:

  • public CoreException(String code): Recibe el código del mensaje de error. Este codigo es el key del mensaje en el archivo de recursos del proyecto.
  • public CoreException(String code,Exception e): Recibe el código del mensaje de error y la excepcion que se quiere anidar.
  • public CoreException(String code, String opt[]): Recibe el código del mensaje de error y un arreglo de parametros que se aplican al mensaje de error.
  • public CoreException(String code, String opt[], Exception e): Recibe el código del mensaje de error, un arreglo de parametros que se aplican al mensaje de error, y la excepcion que se quiere anidar.
  • public CoreException(String control, String code): Recibe el id (parametro control) del componente grafico JSF o RichFaces, sobre el cual se quiere mostrar el mensaje de error, y el código del mensaje de error.
  • public CoreException(String control, String code, String opt[]): Recibe el id (parametro control) del componente grafico JSF o RichFaces, sobre el cual se quiere mostrar el mensaje de error, el código del mensaje de error, y un arreglo con los parametros que se aplican al mensaje de error.

Excepciones propias de un proyecto

Para los proyectos específicos se recomienda crear dos excepciones propias de la aplicación, una de ellas marcada con la anotación @ApplicationException(rollback=true) para que realice rollback y otra marcada @ApplicationException(rollback=false) para que no haga rollback. Estas clases deben heredar de CoreException para aprovechar la funcionalidad de esta ultima y para que el interceptor pueda manejar estas excepciones.

Estas dos excepciones se pueden lanzar desde los métodos de negocio y es responsabilidad del desarrollador identificar si lanza una excepción que cause rollback o una que no lo cause.

Generalmente se debe lanzar una excepción que no cause rollback cuando se hacen validaciones de negocio y que a pesar de que fallen estas validaciones se quiere dejar registros en la base de datos, por ejemplo dejar algún registro de auditoria indicando la operación que realizó el usuario.

Otro caso donde se debe utilizar la excepción que no hace rollback es cuando se lanza desde un componente que puede ser utilizado por diferentes EJBs que implementan diferentes casos de uso, ya que en este caso el EJB del caso de uso es el que sabe como debe actuar frente a la excepción (terminar la transacción o continuar con ella).

@Interceptors({ExceptionInterceptor.class})
public void metodo() throws AppNoRollbackException {

   //antes de la validacion se hacen cambios en la BD que se quieren conservar

   //validacion de negocio
   if (!validacionExitosa){
      throw new AppNoRollbackException("error1", new String[] {"param1"});
   }

}

En el siguiente ejemplo se muestra la forma de lanzar una excepción que hace rollback.

@Interceptors({ExceptionInterceptor.class})
public void metodo() throws AppRollbackException {

   try {

     //modificaciones en BD que se quieren devolver si ocurre un error

   } catch (CoreException e) {
       throw new AppRollbackException("error1", new String[] {"param1"});
   }

}

En el anterior ejemplo se puede ver que la excepción que se lanza recibe en su constructor la excepción que se captura, esto se debe hacer para que no se pierda la traza y permita identificar el origen del error.

Suponiendo que en el bloque try se usa algún componente de Core5, dado que estos componentes lanzan excepciones de tipo CoreException, se debe hacer otro bloque catch para capturar las excepciones de tipo CoreException.

Todos los métodos de negocio deben usar el interceptor de manejo de excepciones y deben hacer catch de CoreException y/o Exception. Dentro de estos catch se debe hacer uso del log para registrar el error ocurrido. En el catch de Exception se debe enmascarar la excepción que ocurre en una excepción propia de la aplicación. Si se desea en el catch de CoreException también se puede enmascarar la excepción ocurrida por una excepción propia de la aplicación.

En caso de requerir excepciones por módulos se debe mantener la misma lógica (excepción que hace rollback y excepción que no) haciendo que las excepciones de cada modulo extiendan de una de las dos excepciones base mencionadas anteriormente.

Comportamiento de una transacción ante excepciones

Es importante tener claro el comportamiento de una transacción cuando ocurre alguna excepción durante ella. A continuación se explican algunas reglas que aclaran este tema:

  • Si se propaga una excepción hasta la capa de control de SEAM este realiza un RollBack sin importar el tipo de excepción que llegue a este.
  • Si se lanza una excepción marcada como rollback=true desde un EJB, sin importar la capa en la que se encuentre, este realiza un rollback y marca la transacción como finalizada. Cualquier operación que se realice dentro de la transacción va a lanzar una excepción TransactionRequiredException.
  • Si se lanza una excepción marcada como rollback=false desde un EJB, y esta es capturada antes de llegar a la capa de control de SEAM no se realiza el Rollback y la transacción puede continuar.
  • Cualquier excepción que extienda de java.lang.Exception tiene el mismo comportamiento que una @ApplicationException(rollback=false)
  • Cualquier excepción que extienda de java.lang.RuntimeException tiene el mismo comportamiento que una @ApplicationException(rollback=true)

Excepciones de persistencia

Cuando se usa JPA para la capa de persistencia pueden ocurrir ciertas excepciones. Es necesario tener claro los tipos de excepciones que pueden ocurrir y la causa de estas excepciones para decidir que se debe hacer.
Estas excepciones tienen una excepción base, la cual es javax.persistence.PersistenceException. A continuación se listan las excepciones hijas y la situación que puede hacer que se lance cada una.

  • EntityExistsException: Generada cuando se intenta persistir una entidad que ya existe. Se realiza rollback sobre la transacción. Si ocurre esta excepción es un indicador de que la lógica puede estar incorrecta, ya que es responsabilidad del desarrollador escribir el código para saber si la entidad se debe actualizar o crear.
  • EntityNotFoundException: Generada cuando se intenta refrescar una entidad y esta ya fue eliminada. Se realiza rollback sobre la transacción.
  • NonUniqueResultException: Generada cuando se solicita un resultado único y la consulta retorna más de un resultado. No genera rollback sobre la transacción. Esta excepción puede ser un indicador de que la consulta que se está realizando necesita más filtros para obtener un único resultado.
  • NoResultException: Generada cuando se solicita un resultado único y la consulta no retorna un valor. No genera rollback sobre la transacción. Esta excepción se puede capturar y dependiendo del caso específico se puede retornar un null o se puede generar un mensaje al usuario.
  • OptimisticLockException: Generada cuando ocurre un conflicto en el bloqueo optimista. Se realiza rollback sobre la transacción. Esta excepción se debe capturar y se puede intentar de nuevo hacer la operación o se puede generar un mensaje al usuario para que intente la operación más tarde.

Presentación de excepciones al usuario

En el archivo pages.xml se pueden configurar las páginas que se deben mostrar cuando ocurra una excepción de algún tipo y esta no sea controlada por las capas inferiores de la aplicación.
SEAM por defecto agrega la configuración para las siguientes excepciones:

  • org.jboss.seam.framework.EntityNotFoundException
  • javax.persistence.EntityNotFoundException
  • javax.persistence.EntityExistsException
  • javax.persistence.OptimisticLockException
  • org.jboss.seam.security.AuthorizationException
  • org.jboss.seam.security.NotLoggedInException
  • javax.faces.application.ViewExpiredException
  • org.jboss.seam.ConcurrentRequestTimeoutException
  • java.lang.Exception

Se recomienda dejar esta configuración.
Por otro lado, las excepciones controladas por el interceptor de Core5 se muestran en la página a la que se redirija según las reglas de navegación propias de la aplicación.
El interceptor obtiene el mensaje de la excepción por medio del método getMessage() y lo agrega al FacesMessages. Si la excepción controlada por el interceptor es CoreException o una que extienda de CoreException, al llamar el método getMessage() se accede al mensaje internacionalizado.

Log para excepciones

En todos los bloques catch donde se capturan excepciones es buena práctica dejar log de nivel ERROR para registrar un mensaje con la información suficiente para detectar la causa del error.
En la sección de manejo de log de este documento se describe la forma como se debe realizar este log.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License