Etiquetas personalizadas en JSP

(Enero de 2006)

Introducción

JSP no sólo nos permite el uso de etiquetas estándar, también admite la creación de etiquetas personalizadas, también denominadas "extensiones de etiquetas". Empezamos con algunos ejemplos sencillos:

  1. Supongamos que deseamos notificar a nuestros clientes el procesamiento de su pedido. Podríamos tener una etiqueta que se encargase de mostrar el nombre del cliente y el texto del mensaje. La siguiente etiqueta:
    
    	<ejemplos:notificarPedido nombre="Patricia Escobar" />
    	
    Podría mostrar el mensaje (ver ejemplo):

            Estimado/a Patricia Escobar:
            Su pedido se ha procesado a partir de Wed Feb 01 11:10:52 CET 2006. En el plazo de tres días recibirá la mercancía.

    La etiqueta redirige la salida a una clase que se encarga de definir el resultado, a esta clase se la denomina interprete o manejador de etiquetas.



  2. Otro ejemplo es la creación de una etiqueta que realizase alguna labor de validación, por ejemplo, comprobar que el usuario es un cliente registrado en la base de datos. La ventaja es clara, el desarrollador de las páginas puede acceder a una fuente de datos encapsulada en etiquetas, es decir, le queda oculta la complejidad del tratamiento de los datos. Se reduce o elimina el uso de scriptlets.


  3. Podriamos manejar una iteración sin necesidad de scriptlets.

En resumen, la idea es sencilla: invocar mediante etiquetas a componentes comunes y reusables, que impliquen tanto presentación como lógica de negocio. En la actualidad muchos vendedores de software ofertan dentro de sus productos no sólo JavaBeans, sino además etiquetas "extendidas" para facilitar el trabajo de integración y desarrollo; esto es un ejemplo típico de reusabilidad.

Resumen previo

Los elementos necesarios para llevar a cabo la tarea son los siguientes:

lo que haremos a continuación es examinar cada uno de estos elementos.

La clase

Cuando el motor JSP se encuentre con la etiqueta, realiza una serie de llamadas al objeto que implementa el comportamiento de la etiqueta, con la finalidad de que dicha clase escriba en la salida del JSP (le cede su salida).

El caso más común, en el que la clase hereda de TagSupport (que implementa el interfaz javax.servlet.jsp.tagext.Tag), los mensajes que el contenedor envía son (en el orden indicado):

  1. setPageContext( pageContext ): el contenedor envía el contexto de la página.


  2. setParent( enclosingTag ): será null si la etiqueta no está incluida en otra etiqueta (su progenitora).


  3. Llamada a los métodos setXXX() de la clase para definir el valor de cada atributo de la etiqueta. Hay conversión automática de tipos desde el atributo (String) al tipo de la clase; si falla, se lanza una excepción.


  4. doStartTag(): llamada cuando el contenedor termina de interpretar el inicio de la etiqueta. Devuelve EVAL_BODY_INCLUDE, que indica que evalúe el cuerpo de la etiqueta (si lo tiene). Devuelve SKIP_BODY en caso de que no realice dicha evaluación.


  5. doEndTag(): llamada cuando el contenedor lee el final de la etiqueta. Debe devolver EVAL_PAGE si quiere que el contenedor continúe evaluando la página JSP. En raras ocasiones (tal vez si el permiso del usuario no ha sido validado positivamente), se devuelve SKIP_PAGE para que no evalue el resto de la JSP.
  6. release(): llamada inmediatamente antes de que entre en la recolección y limpieza de basura. El programador sólo tendrá que implementar comportamiento si hay que liberar algún recurso (conexión JDBC, socket, etc.).

Buenas noticias: normalmente el programador sólo tiene que trabajar sobre un método (doStartTag() o doEndTag() para definir la salida. El resto de métodos son heredados de la clase madre. El método heredado doStartTag() devuelve EVAL_BODY_INCLUDE. El objeto pageContext también es heredado.

En nuestro ejemplo se implementa el comportamiento de la etiqueta en doEndTag(). Pero sea cual sea el método elegido (Start/End), se trabaja en último término contra la salida, que es obtenida por medio del contexto de página, pageContext.getOut().

El código de ejemplo:


package paq_08_taglib_atrib;

import java.io.IOException;
import java.util.Date;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

/************************************************************************
 * Clase que interpreta etiqueta.
 * Notifica al usuario cuyo nombre es el atributo 'nombre' la recepción del pedido. 
 ************************************************************************/
public class Notificacion  extends TagSupport {

	  private String nombre;   // Nombre del usuario

	  /************************************************************************
	   * Aunque no se use explicitamente por parte del programador,
	   * resulta necesario internamente por parte de esta clase para
	   * obtener el valor que pasa la página JSP 
	   ************************************************************************/
	  public String getNombre() { return nombre; }

	  /************************************************************************
	   *  El motor JSP invoca a este método antes de invocar a doStartTag()
	   ************************************************************************/
	  public void setNombre( String nombre ) {
	    this.nombre = nombre;
	  }

	  /************************************************************************
	   *  El motor JSP invoca a este método al inicio de la etiqueta.
	   *  No es necesario: si se omite, se hereda
	   ************************************************************************/
	  public int doStartTag() throws JspTagException {
	    return EVAL_BODY_INCLUDE;
	  }

	  /************************************************************************
	   *  El motor JSP invoca a este método al final de la etiqueta.
	   *  Es el responsable de la escritura del resultado.
	   ************************************************************************/
	  public int doEndTag() throws JspTagException {
	    String fecha = new Date().toString();
	    try {
	      pageContext.getOut().write("Estimado/a <b>" + getNombre() + "</b>:");
	      pageContext.getOut().write("<p>Su pedido se ha procesado a partir de " + 
	                                 fecha + ". En el plazo de tres días recibirá la mercancía.");
	    } catch (IOException ex) {
	      throw new JspTagException("Error al escribir etiqueta en la salida JSP");
	    }
	    return EVAL_PAGE;
	  }
}
	

Observamos que la IOException debe lanzar una JspTagException con la finalidad de poder gestionar la excepción en la página.

El descriptor de la librería de etiquetas

El descriptor tiene como finalidad especificar la etiqueta, con sus atributos, cuerpo, etc. En nuestro ejemplo vemos que requiere un archivo DTD (definición de tipos):


<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
      PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
      "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
  <tlib-version>1.0</tlib-version>
  <jsp-version>1.2</jsp-version>
  <short-name>ejemplos</short-name>
  <description>Ejemplos de etiquetas personalizadas</description>

  <tag>
    <name>notificarPedido</name>
    <tag-class>paq_08_taglib_atrib.Notificacion</tag-class>
    <body-content>JSP</body-content>
    <description>Ejemplo sencillo con atributos</description>
    <attribute>
      <name>nombre</name>
      <required>true</required>
      <rtexprvalue>true</rtexprvalue>
    </attribute>
  </tag>
  
</taglib>

Algunos comentarios al respecto de la librería:

Respecto a la etiqueta:

Respecto al atributo:

Habitualmente se pone el TLD en un subdirectorio de WEB-INF (en lib o en tlds), con la finalidad de que quede protegido.

La página

El código JSP no hace más que importar la librería donde se encuentra nuestra etiqueta e invocar dicha etiqueta:

web.xml

Én web.xml hay que añadir las siguientes líneas para nuestro ejemplo:


  <taglib>
    <taglib-uri>/ejemplos</taglib-uri>
    <taglib-location>/WEB-INF/tlds/ejemplos.tld</taglib-location>
  </taglib>
	

Si se usa Tomcat aconsejo que se situe taglib entre servlet-mapping y resource-ref. El orden importa y dependiendo de la versión y la configuración del servidor puede ocurrir que nos de una excepción a la hora de interpretar el web.xml.

taglib-uri hace referencia a la dirección que aparece en la página JSP y taglib-location hace referencia a la ruta física (dentro de nuestro contexto de aplicación).

Si no se pone taglib o no coincide su taglib-uri con la uri de la JSP nos puede aparecer el típico ejemplo de "Archivo "XXX" no encontrado".

Atributos complejos

Hasta ahora hemos usado atributos que son tipos "simples" (String por ejemplo), supongamos que necesitamos tipos más complejos, por ejemplo un Vector.

En el siguiente ejemplo tenemos un nuevo atributo, denominado 'amigos', que es un vector de nombres de amigos del usuario que en este momento están en línea dentro del chat. La página sería:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page language="java" contentType="text/html; charset=iso-8859-1" %>
<%@ page errorPage="07_errorPage.jsp"%>
<%@ taglib uri="/ejemplos" prefix="ejemplos" %>
<%@ page import="java.util.Vector"%>
<%
	Vector v = new Vector();
	v.add( new String("Ana") );
	v.add( new String("Susana") );
	v.add( new String("Juan") );
%>

<html>
  <head>
    <title>Etiquetas personalizadas</title>
  </head>
  <body>
    <h1>Etiquetas personalizadas</h1>
	<p>Te indicamos tus amigo en linea:</p>
	<ejemplos:notificarAmigos nombre="Patricia Escobar" amigos="<%=v%>"/>
  </body>
</html>
	

Enlace al ejemplo. Se deja al lector el ejercicio de modificar el resto de elementos para que este ejemplo tenga éxito.

El cuerpo de las etiquetas

¿Dónde se imprime el cuerpo de la etiqueta. La respuesta es sencilla, dependiendo del método utilizado para definir la salida. Si hemos escogido doEndtag primero aparece el cuerpo y luego el valor de la etiqueta. Si hemos escogido doStartTag aparecerá inmediatamente después del contenido de la etiqueta. Un ejemplo de JSP con cuerpo:


	<% int numPedidos = 3;%>
	<ejemplos:notificarPedido nombre="Manolo" >
		Esto es el 'cuerpo' de Manolo. Tiene <%=numPedidos%> pedidos pendientes.<br><br>
	</ejemplos:notificarPedido>
	



Volver al índice