Manejar Excepciones

¿Que sucedió la última vez que usamos una aplicación JSP e introdujimos algo incorrectamente? Si la aplicación estaba bien escrita, probablemente lanzaría una excepción y mostraría una página de error. Las excepciones que ocurren durante la ejecución de una aplicación JSP se llaman excepciones en tiempo de ejecución y se describen en este tutorial.

Al igual que en una aplicación Java, una excepción es un objeto que es un ejemplar de java.lang.Throwable o de una de sus subclases. Throwable tiene dos subclases estándards -java.lang.Exception, que describe excepciones, y java.lang.Error, que describe errores.

Los errores son diferentes de las excepciones. Los errores normalmente indican problemas de enlaces o de la máquina virtual de los que nuestra aplicación Web podría no recuperarse, como errores de memoria. Sin embargo, las excepciones son condiciones que pueden capturarse y recuperarse de ellas. Estas excepciones podrían ser, por ejemplo, un NullPointerException o un ClassCastException, que nos dicen que se ha pasado un valor nulo o un dato del tipo erróneo a nuestra aplicación mientras se estaba ejecutando.

Las excepciones en tiempo de ejecución son fáciles de menejar en una aplicación JSP, porque estan almacenadas una cada vez en el objeto implícito llamado exception. Podemos usar el objeto exception en un tipo especial de página JSP llamado página de error, donde mostramos el nombre de la clase exception, su seguimiento de pila, y un mensaje informativo para el usuario.

Las excepciones en tiempo de ejecución son lanzadas por el fichero JSP compilado, el fichero class Java que contiene la versión traducida de nuestra página JSP. Esto significa que nuestra aplicación ha sido compilada y traducida correctamente. (Las excepciones que ocurren mientras un fichero está siendo compilado o traducido no son almacenadas en el objetoexception y tienen sus mensajes mostrados en la ventana de comandos, en vez de en la página de error. Estas no son el tipo de excepciones descritas en este tutorial.)

Este tutorial describe cómo crear una sencilla aplicación JSP con varias páginas, un componente JavaBean y una página de error que ofrece mensajes informativos al usuario. En este ejemplo, el Bean sigue la pista sobre la página en la que estaba trabajando el usuario cuando se lanzó la excepción, que nos da a nosotros, el desarrollador, información útil para que podamos mostrar un mensaje informativo. Este es un simple mecanismo de seguimiento de error.

¿Cómo Añadir Páginas de Error?

Aunque las llamemos páginas de error, las páginas especializadas JSP que describimos aquí realmente muestran información sobre excepciones. Para añadir páginas de error que muestren información de excepciones a una aplicación web, seguimos estos pasos:

Ejemplo de Buscador de Direcciones de Email

Este ejemplo, llamado email, almacena nombres y direcciones de e-amil en un fichero map basado en la clase java.util.TreeMap definida en el JDK 1.2. La clase TreeMap crea una estructura de datos llamada "red-black tree". En el árbol, los datos son almacenados con una clave y un valor. En este ejemplo, el nombre es la clave y la dirección email el valor.

Cuando añadimos una entrada al fichero map, introducimos tanto un nombre (la clave) como una dirección email (el valor). Podemos buscar o borrar una dirección email introduciendo sólo un nombre. El nombre no puede se null porque es una clave. Si un usuario intenta introducir un nombre null, la aplicación lanza una excepción y muestra una página de error.

¿Entonces que es un Red-Black Tree?

Para aquellos que seamos curiosos sobre algoritmos, un árbol rojo-negro es un arbol binario extendido que se parece a algo similar a esto (conceptualmente, al menos):

Si estas viendo este documento en la pantalla, veras que algunos nodos son rojos y otros on negros.

El árbol rojo-negro tiene nodos que pueden ser ramas u hojas. Los nodos hojas son los nodos que hay al final de una línea, mientras que los nodos ramas son los nodos más grandes que conectan con dos o más líneas. Los nodos se almacenan en una estructura compensada en el árbol, usando las siguientes condiciones:

La ventaja de un árbol, para nosotros, los desarrolladores Web, es que podemos crear un fichero map que almacena datos en orden ascendente (ordenados por claves) y que tiene tiempos de búsqueda rápidos.

¿Cómo está Estructurado el Ejemplo?

El ejemplo email tiene tres páginas con formularios HTML, dos ficheros de respuesta, una página de error, y un componente JavaBean. Podemos visualizar la estructura de ficheros en algo como esto:

Añadir un Nombre y una Dirección Email (email.jsp)

<%@ include file="copyright.html" %>

<%@ page isThreadSafe="false" import="java.util.*, email.Map"
	errorPage="error.jsp" %>

<jsp:useBean id="mymap" scope="session" class="email.Map" />
<jsp:setProperty name="mymap" property="name" param="name" />
<jsp:setProperty name="mymap" property="email" param="email" />

<% mymap.setAction( "add" );  %>

<html>

<head><title>Email Finder</title></head>
<body bgcolor="#ffffff" background="background.gif" link="#000099">

<!-- the form table -->

<form method="get">
<table border="0" cellspacing="0" cellpadding="5">

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> <h1>Email Finder</h1> </td>
</tr>

<tr>
<td width="120" align="right"><b>Name</b></td>
<td align="left"><input type="text" name="name" size="35"></td>
</tr>

<tr>
<td width="120" align="right"><b>Email Address</b></td>
<td align="left"><input type="text" name="email" size="35"></td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
Please enter a name and an email address.
</td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
<input type="submit" value="Add">
</td>
</tr>

<!-- here we call the put method to add the 
          name and email address to the map file -->

<%
	String rname = request.getParameter( "name" );
	String remail = request.getParameter( "email" );
	if ( rname != null) {
		mymap.put( rname, remail );
	} 
%>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
The map file has <font color="blue"><%= mymap.size() %>
</font> entries.
</font>
</td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
<a href="lookup.jsp">Lookup</a>&nbsp; | &nbsp;
	<a href="delete.jsp">Delete</a>
</td>
</tr>

</table>
</form>

</body>
</html>

Buscar un Nombre en el Fichero Map (lookup.jsp)

<%@ include file="copyright.html" %>

<%@ page isThreadSafe="false" import="java.util.*, email.Map"
	errorPage="error.jsp" %>

<jsp:useBean id="mymap" scope="session" class="email.Map" />
<jsp:setProperty name="mymap" property="name" param="name" />

<% mymap.setAction( "lookup" ); %>

<html>
<head><title> Email Finder </title></head>
<body bgcolor="#ffffff" background="background.gif" link="#000099">

<form method="get">
<table border="0" cellspacing="0" cellpadding="5">

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> <h1>Email Finder</h1> </td>
</tr>

<tr>
<td width="120" align="right"><b>Name</b></td>
<td align="left"><input type="text" name="name" size="35"></td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> 
Please enter a name for which 
<br>
you'd like an email address.
</td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
The map file has <font color="blue"> <%= mymap.size() %></font> 
entries.
</td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> <input type="submit" value="Lookup"> </td>
</tr>

<%  if ( request.getParameter( "name" ) != null ) {  %>
	<%@ include file="lookupresponse.jsp" %> 
<%  }  %> 

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
<a href="email.jsp">Add</a> &nbsp; | &nbsp; 
	<a href="delete.jsp">Delete</a>
</td>
</tr>
</table> 

</body>
</html>

Mostrar la Respuesta a la Búsqueda (lookupresponse.jsp)

<%@ page import="java.util.*, email.Map"  %>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
<b> Success! </b>
</td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
<jsp:getProperty name="mymap" property="name" />
<br>
<jsp:getProperty name="mymap" property="email" />
</td>
</tr>

Borrar una Dirección Email (delete.jsp)

<%@ include file="copyright.html" %>

<%@ page isThreadSafe="false" import="java.util.*, email.Map" 
	errorPage="error.jsp" %>

<jsp:useBean id="mymap" scope="session" class="email.Map" />
<jsp:setProperty name="mymap" property="name" param="name" />

<!-- tags the JSP page so that we can display 
          the right exception message later -->

<% mymap.setAction( "delete" ); %>

<html>
<head><title> Email Finder </title></head>
<body bgcolor="#ffffff" background="background.gif" link="#000099">

<form method="get">
<table border="0" cellspacing="0" cellpadding="5">

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> <h1>Email Finder</h1> </td>
</tr>

<tr>
<td width="120" align="right"><b>Name</b></td>
<td align="left"> <input type="text" name="name" size="35"> </td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> 
Please enter a name you would like to delete.
</td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
The map file has <font color="blue"> <%= mymap.size() %></font> 
entries.
</td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> <input type="submit" value="Delete"> </td>
</tr> 

<!-- display the name and email address, then 
          delete them from the map file -->

<%  if ( request.getParameter( "name" ) != null ) {  %>
	  <%@ include file="deleteresponse.jsp" %>
<%
	  mymap.remove( request.getParameter("name") ) ;
	} 
%>

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
<a href="email.jsp">Add</a> &nbsp; | &nbsp;
	<a href="lookup.jsp">Lookup</a>
</td>
</tr>

</table>
</body>
</html>

Mostrar la Respuesta de Borrado (deleteresponse.jsp)

<%@ page import="java.util.*, email.Map"  %>

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> <b>Success!</b> </td>
</tr>

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> 
<jsp:getProperty name="mymap" property="name" />
<br>
<jsp:getProperty name="mymap" property="email" />
<br><p>
has been deleted from the map file.
</td>
</tr>

Mostrar Mensajes de Excepción (error.jsp)

<%@ include file="copyright.html" %>

<%@ page isErrorPage="true" import="java.util.*, email.Map" %>
<jsp:useBean id="mymap" scope="session" class="email.Map" />

<html>
<head><title>Email Finder</title></head>
<body bgcolor="#ffffff" background="background.gif" link="#000099">

<table border="0" cellspacing="0" cellpadding="5">
<tr>
<td width="150" align="right"> &nbsp; </td>
<td align="right" valign="bottom"> <h1> Email Finder </h1> </td>
</tr>

<tr>
<td width="150" align="right"> &nbsp; </td>
<td align="right"> <b>Oops! an exception occurred.</b> </td>
</tr>

<tr>
<td width="150" align="right"> &nbsp; </td>
<td align="right">The name of the exception is 
	<%= exception.toString() %>. 
</td>
</tr>

<tr>
<td width="150" align="right"> &nbsp; </td>
<td align="right"> &nbsp; </td>
</tr>

<% if (mymap.getAction() == "delete" ) {  %>
	<tr>
	<td width=150 align=right> &nbsp; </td>
	<td align=right>
	<b>This means that ...</b>
	<p>The entry you were trying to 
	<font color="blue">delete</font> is not in the map file <br>
	<b><i>or</i></b>
	<br>
	you did not enter a name to delete.
	<p>
	Want to try <a href="delete.jsp">again</a>?
	</td>
	</tr>
	
<% }

else if (mymap.getAction() == "lookup" ) { %>
	<tr>
	<td width="150" align="right"> &nbsp; </td>
	<td align="right">
	<b><i>This means that ...</b></i>
	<p>the entry you were trying to 
	<font color="blue">look up</font>
	is not in the map file, <b><i>or</i></b>
	<br>
	you did not enter a name to look up.
	<p>
	Want to try <a href="lookup.jsp">again</a>?
	</td>
	</tr>

<% } 

else if (mymap.getAction() == "add" ) { %>
	<tr>
	<td width="150" align="right"> &nbsp; </td>
	<td align="right">
	<b><i>This means that ...</b></i>
	<p>You were trying to <font color="blue">add</font>
	an entry with a name of null.
	<br>
	The map file doesn't allow this.
	<p>
	Want to try <a href="email.jsp">again</a>?
	</td>
	</tr>

<%  }  %>

</table>

Crear el Fichero Map (Map.java)

package email;
import java.util.*;

public class Map extends TreeMap {

// In this treemap, name is the key and email is the value

	private String name, email, action;
	private int count = 0; 

	public Map() { } 

	public void setName( String formName ) {
		if ( formName != "" ) {
			name = formName; 	 
		}	
	}

	public String getName()
		return name; 
	}	

	public void setEmail( String formEmail ) {
		if ( formEmail != "" ) {	 
		  email = formEmail;			 
		  System.out.println( name );  // for debugging only
		  System.out.println( email );  // for debugging only
		}	
	}

	public String getEmail() {
		email = get(name).toString();
		return email; 	
	}

	public void setAction( String pageAction ) {
		action = pageAction;	 	
	}

	public String getAction() {
		return action;	
	}

}

Manejar Excepciones en el Bean

En este ejemplo, el código que lanza excepciones es la clase TreeMap, que extiende nuestro email.Map, por eso no tenemos que escribir código que lance excepciones en el Bean.

Los métodos que hemos usado de TreeMap son estos con sus excepciones:

Por supuesto, si necesitamos más información sobre estos métodos, podemos buscarlos en el API Javadoc por java.util.TreeMap.

La clase TreeMap lanza una ClassCastException cuando el usuario trata de introducir un dato del tipo erróneo en unf ichero map, por ejemplo, un int donde el fichero map está esperando un String. Tengamos en cuenta que la clase TreeMap también se usa en aplicaciones cliente Java. En nuestra aplicación JSP, esta aplicación no ocurrirá, porque el usuario introduce un nombre y una dirección email en un formulario HTML, que siempre pasa los datos al Bean como strings. Incluso si el usuario teclea 6 como un nombre, el valor envíado es un String.

Sin embargo, los métodos get, put, y remove lanzan una NullPointerException si el usuario no introduce nada o se pasa un valor null al Bean. Esta la excepción más comun que necesita manejar la aplicación email. Esta excepción podría ocurrir siempre que el usuario intente añadir, buscar o eliminar una entrada del fichero map. Recuerda que la clave, (en este caso el nombre) no peude ser null.

Cuando el Usuario Intenta Añadir un Valor Null
El primer caso, cuando el usuario intenta añadir un nombre o dirección de email nulos, es manejado por un sencillo código en el Bean y en email.jsp. (Aquí null significa que el usuario no ha introducido nada en la caja de texto del formulario. No maneja el caso en que el usuario teclee uno o dos espacio en blanco, y luego pulsa Return).

El código que maneja la adicción de valores null está en los métodos setName y setEmail de Map.java y en un scriptlet en email.jsp:

Capturar un Valor Null Durante la Adicción
Map.java:

public void setName( String formName ) {
	if ( formName != "" ) {
		name = formName; 	 
	}
}
public void setEmail( String formEmail ) {
	if ( formEmail != "" ) {	 
	   email = formEmail;	
	   System.out.println( name ); // for debugging only
	   System.out.println( email ); // for debugging only 	 
	}	
}

email.jsp:

<%
	String rname = request.getParameter( "name" );
	String remail = request.getParameter( "email" );
	if ( rname != null) {
		mymap.put( rname, remail );
	}
%>
Tanto setName como setEmail chequean su el usuario ha introducido un valor en el formulario antes de seleccionar sus respectivas propiedades. Si el formulario es un valor null, el Bean no selecciona ninguna propiedad, el método put no añade nada al fichero map, y no se lanza ninguna excepción.
Cuando el Usuario Intenta Buscar un Valor Null
Pero si vamos a las páginas Lookup o Delete del ejemplo e intentamos buscar o borrar una entrada que no está en el fichero map, la aplicación email lanza una NullPointerException y muestra una página de error.
Capturar un Valor Null durante la Búsqueda
lookup.jsp:

<%  if ( request.getParameter( "name" ) != null ) {  %>
	  <%@ include file="lookupresponse.jsp" %>
<%  }  %>

lookupresponse.jsp:

<tr>
<td width="120"> &nbsp; </td>
<td align="right">
<font face="helvetica" size="-2">
<jsp:getProperty name="mymap" property="name" />
<br>
<jsp:getProperty name="mymap" property="email" />
</font>
</td>
</tr>
Este ejemplo tiene dos piezas de código que trabajan juntas. La página lookup.jsp, donde introducimos un nombre por el que queremos buscar en el fichero map, tiene un scriptlet que chequea si el usuario ha introducido un nombre en el formulario o no. Si el usuario no ha introducido un nombre o introduce uno que no existe en el fichero map, el Bean lanza una NullPointerException y la aplicación muestra una página de error -- que es el comportamiento deseado! En este caso, podemos estar felices porque se muestra la página de error.

Podríamos haber observado que las líneas del fichero lookupresponse.jsp usan la etiqueta <jsp:getProperty> para recuperar el nombre y la dirección email desde el Bean. También podríamos intentar recuperar la dirección email usando expresiones, algo como esto:

<%= request.getParameter( "name" ) %>
<br>
<%= mymap.get( request.getParameter( "name" ) ) %>
Si usamos estas líneas, el comportamiento de la aplicación sería un poco diferente. En vez de lanzar una NullPointerException y mostrar una página de error, mostraría el nombre que introdujo el usuario, con la palabra null debajo en la página JSP. En la implementación JSP de Sun, la etiqueta <jsp:getProperty> maneja intencionadamente las valores null de forma diferente que los scriptlets o las expresiones. La forma de manejar los valores Null depende del motor JSP utilizado.
Cuando el Usuario Intenta Borrar un Valor Null
Manejar el caso de un usuario que intenta borrar un valor null es muy similar a manejar la búsqueda de un valor null.
Capturar un Valor Null durante el Borrado
delete.jsp:

<% if ( request.getParameter( "name" ) != null ) {  %>
	 <%@ include file="deleteresponse.jsp" %>
<%
	mymap.remove( request.getParameter("name") ) ;
	} 
%>

deleteresponse.jsp:

<tr>
<td width="120"> &nbsp; </td>
<td align="right"> 
<font face="helvetica" size="-2"> 
<jsp:getProperty name="mymap" property="name" />
<br>
<jsp:getProperty name="mymap" property="email" />
<br><p>
has been deleted from the map file.
</font> 
</td>
</tr>

Llamar a una Página de Error desde otra Página

Para hacer que las páginas muestren una página de error, cada página de la aplicación email usa una directiva page con el atributo errorPage, de esta forma:
<%@ page isThreadSafe="false" import="java.util.*, email.Map"
	errorPage="error.jsp" %>
En los ejemplos de código, los ficheros que usan esta directiva son email.jsp, lookup.jsp, y delete.jsp. Sólo podemos especificar un página de error por cada página JSP.

Esto significa que podemos diseñar una aplicación JSP para que cada página JSP llame a una página de error diferente, o que varias páginas JSP llamen a un misma página de error. En la aplicación email, varias páginas JSP llaman a un página de error, a sí simplificamos el número de ficheros que necesitamos para mantener una aplicación.

Deberíamos usar al menos una página de error en una aplicación JSP. Si no especificamos una página de error, los mensajes de excepción y el seguimiento de pila se mostrarán en la ventana de comandos desde la que se arrancó el motor JSP, mientras que el navegador Web mostrará un mensaje de error HTTP no informativo, por ejemplo, un mensaje 404 o 501. Esta definitivamente no es una manera adecuada de manejar excepciones.

Escribir una Página de Error

Una página de error es diferente a una página normal JSP. En una página de error, debemos seleccionar explícitamente el atributo isErrorPage de la directiva page como true. También tendremos acceso al objeto exception, que nos dará información sobre la excepción.

Primero, veamos un ejemplo de la directiva page de una página de error:

<%@ page isErrorPage="true" import="java.util.*, email.Map" %>
Una vez que hemos seleccionado isErrorPage a true, podemos usar el objeto exception. exception es del tipo java.lang.Throwable, por eso podemos usar cualquier método definido en Throwable con exception en un scriptlet o una expresión, por ejemplo:

La expresión exception.toString() muestra el nombre de la clase de la excepción, por ejemplo, java.lang.NullPointerException, mientras que exception.printStackTrace() muestra el seguimiento de pila de la excepción. El nombre de la clase y el seguimiento de pila son probablemente muy útiles para nuestro usuario. Para evitar esto, podríamos querer escribir algún tipo de mecanismo de seguimiento para proporcionar información que nos ayude a darle un mensaje informativo a nuestro usuario.

Escribir un Sencillo Mecanismo de Pila

El ejemplo email usa una propiedad llamada action en Map.java para seguir la página en la que el usuario estaba trabajando cuando se lanzó la excepción. Esto nos da información importante para ayudarnos a escribir un mensaje de error informativo para el usuario. El Bean tiene una variable llamada action, un método getAction, y un método setAction. La declaraciones de variable y métodos en el Bean se parecen a esto:
private String action;

public void setAction( String pageAction ) {
	action = pageAction; 	 	
}

public String getAction() {
	return action;	
}
Cada una de las páginas email.jsp, lookup.jsp, y delete.jsp seleccionan el valor de action con una línea como esta (que viene desde email.jsp):
<% mymap.setAction( "add" ); %>
Si ocurre una excepción, error.jsp chequea el valor de action e incluye el mensaje apropiado para cada valor, usando líneas como estas:
<% if (mymap.getAction() == "delete" ) {  %>
.. text message here ..
else if (mymap.getAction() == "lookup" ) { %>
.. text message here ..
else if (mymap.getAction() == "add" ) { %>
.. text message here ..
<%  }  %>
Por supuesto, esta es una forma sencilla de implementar seguimiento. Si nos movemos dentro del desarrollo de aplicaciones J2EE con beans enterprise, podemos escribir aplicaciones que graben el estado.

¿Cómo ejecuar el Ejemplo?

Para poder ejecutar este ejemplo, necesitamos tener instalado el JDK 1.2 (si no lo tienes, puedes ir a http://java.sun.com/products/OV_jdkProduct.html.)

Los paths dados aquí son para un sistema UNIX, si estás usando Windows, deberás usar los mismos paths pero con el separador de directorios invertido:

  1. Creamos el directorio (o carpeta) ../jswdk-1.0/examples/jsp/tutorial/email.
  2. Situamos los siguientes ficheros en el directorio ../tutorial/email: background.gif, delete.jsp, deleteresponse.jsp, email.jsp, error.jsp, lookup.jsp, lookupresponse.jsp.
  3. Creamos el directorio (o carpeta) ../jswdk-1.0/examples/WEB-INF/jsp/beans/email.
  4. Situamos los ficheros Map.class y Map.java en el directorio ../beans/email.
  5. Arrancamos la implementación de referencia JSP de Sun:cd ../jswdk-1.0startserver
  6. Abrimos un navegador Web y vamos a: http://yourMachineName:8080/examples/jsp/
    tutorial/email/email.jsp

Ozito