Excepciones Seguras De Deserializar
En Java, cuando se expone un método para ser invocado en forma remota, por ejemplo en un EJB, puede suceder que tire una excepción como respuesta a la invocación como consecuencia de algún error durante la ejecución. Esta excepción será serializada y pasada por red al método que realizó la invocación.
Cuando llega la excepción del otro lado tiene que volver a hidratarse para lo que es necesario contar con la definición de la clase a deserializar.
Con un poco de cuidado y haciendo las cosas bien, la clase de la Excepción existirá del lado del cliente. Pero también debemos tener en cuenta que las excepciones suelen tener anidada la causa que las produjo (otra excepción) y la causa puede tener anidada a su vez otra causa y así sucesivamente hasta llegar a la raíz del problema que originó el fallo.
Todas las clases de la cadena de causas deberá estar presente del lado del cliente que invoca el método remoto ya que intentará rehidratarlas. A veces, por alguna razón u otra, puede ser que no tengamos disponible las excepciones de alguna parte de la cadena de causas del lado del cliente. Deberíamos intentar solucionar este problema investigando porqué no están esas excepciones e incluirlas.
Como último recurso, si no fuera posible asegurar su existencia del lado del cliente, este ejemplo muestra como reemplazar las excepciones de la cadena por excepciones que estamos seguros que pueden ser hidratadas nuevamente.
Objetivo
El objetivo es eliminar el problema de la rehidratación de una excepción del lado del cliente con la mínima pérdida de información posible.
Una solución rápida sería cortar la cadena de causas, pero esto nos dejaría ciegos frente a un problema.
Otra solución sería arrojar una excepción que copie los stack trace de todas sus causas y organice material descriptivo, pero dejaría la información inaccesible con tratamientos estándar.
Finalmente se plantea reemplazar las excepciones de la cadena de causas con excepciones que estamos seguros que se pueden rehidratar del lado del cliente y se copia la información del mensaje y el stack trace de las causas originales.
Con esta solución, se sigue recibiendo excepciones encadenadas y se conserva el stack trace en cada una de ellas en forma estándar.
El costo que tiene es que se pierde la clase original de la excepción que se reemplace. Es por esto que debe utilizarse a conciencia.
Posible mejora
El ejemplo que se plantea hace un reemplazo masivo de las causas en el último momento posible. Una mejora importante sería discriminar que excepciones pueden no ser reemplazadas para conservar intacta la mayor parte posible de la cadena.
Escenario
Una clase Blah que tiene un métodoX que se invocará remotamente.
- Se envuelve su funcionamiento en un bloque try para atajar las excepciones que se produzcan.
- En el catch se reemplaza la excepción junto con su cadena de causas por excepciones que es seguro que se pueden rehidratar del lado del cliente.
public class Blah {
public void metodoX() throws RemoteException { try { //Lo que hace el metodoX } catch (Exception e) { throw HidratacionSegura.asegurarComoRuntime(e); } }
}
Reemplazo recursivo
La funcionalidad de reemplazo se encapsula en un método estático en una clase ad hoc. Este método toma una excepción y la reemplaza por una RuntimeException copiando el mensaje, el stack trace y la causa. A su vez, antes de copiar la causa, la reemplaza llamándose a si mismo. De esta manera reemplaza toda la cadena en forma recursiva.
public class HidrataciónSegura {
public static RuntimeException asegurarComoRuntime(Throwable t) {
if (t == null) { return null; }
RuntimeException causaSegura = asegurarComoRuntime(t.getCause());
String mensaje = "[Reemplazo de " + t.getClass().getName() + "] " + t.getMessage();
RuntimeException excepcionSegura = new RuntimeException(mensaje, causaSegura);
excepcionSegura.setStackTrace(t.getStackTrace());
return excepcionSegura;
}
}