Estructura de las Páginas JSP
- Accediendo a MySQL con Etiquetas

Con esta sección terminamos el curso del JSP‘s. Aquí consolidaremos todo lo visto anteriormente.


1. Accediendo a MySQL con Etiquetas

En esta sección realizaremos un ejercicio en el que consolidaremos etiquetas personalizadas, variables scripting y acceso a base de datos.

Además, ahora implementaremos el interface BodyTag. Este interface es importante cuando se tiene que manipular el contenido del cuerpo (de una etiqueta), o cuando se tiene que iterar y mostrar un contenido diferente cada vez.

El interface BodyTag extiende al interface Tag (ambos parte del paquete javax.servlet.jsp.tagext. Como se ha mencionado previamente, para hacer una etiqueta personalizada, uno debe implementar alguno de estos interfaces. Esto se puede hacer directamente (que es lo que hemos venido haciendo), lo cual resulta más eficiente, o se puede hacer por extener las clases TagSupport o BodyTagSupport, las cuales proveen una implementación por omisión para estos interfaces respectivamente.

El interface BodyTag contiene los seis métodos (ya vistos anteriormente) del interface Tag, y tres otros métodos que permiten hacer lo siguiente:

Como cualquier cosa en Java, el contenido del cuerpo está representado como un objeto, esto es, el contenido del cuerpo tiene una clase asociada llamada BodyContent.

Los métodos incluidos por el interface BodyTag son los siguientes (los tres nuevos métodos del interface aparecen remarcados):

  setPageContext( PageContext p )
  setParent( Tag t )
  getParent()
  doStartTag()
  setBodyContent( BodyContent b )
  doInitBody()
  doAfterBody()
  doEndTag()
  release()
  1. Los métodos setPageContext() y setParent() son invocados por la página JSP para hacer disponible los objetos PageContext y la etiqueta padre (si una etiqueta no tiene una etiqueta padre, entonces no tiene ningún uso este método). Está la contraparte de setParent(), que es getParent().
  2. El método doStartTag() es invocado cuando se llega a la etiqueta de apertura (este método sólo se invoca una vez).
  3. El método setBodyContent() es invocado por la página JSP para hacer disponible al contenido del cuerpo de la etiqueta en la forma de una clase BodyContent. La clase BodyContent extiende a la clase JspWriter y por lo tanto puede ser utilizada para enviar información de regreso al cliente. Si la etiqueta no tiene contenido en su cuerpo, el método setBodyContent() no será invocado.
  4. El método doInitBody() (al igual que setBodyContent()), sólo será llamado si la etiqueta tiene contenido en su cuerpo.
  5. El método doAfterBody() es invocado cuando el contenido del cuerpo de la etiqueta ha sido evaluado. Es en este punto que se puede acceder y manipular el contenido del cuerpo si se requiere. Así que este método es vital. De la misma forma que en los dos métodos anteriores, doAfterBody() no será invocado si la etiqueta no tiene contenido en su cuerpo, o si el método doStartTag() regresa el valor SKIP_BODY.
  6. El método doEndTag() es invocado cuando se llega a la etiqueta de cierre.
  7. El método release() es el último método a invocar, y como ya se mencionó previamente, se utiliza para liberar los recursos utilizados por los métodos anteriores.

1.a. Construcción Paso a Paso

Empezamos con la definición del paquete al que pertence la clase p161_DbTag:

package pkgs.pq05;

Importamos los paquetes requeridos por la clase:

import java.io.*;
import java.sql.*;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

Hacemos la declaración de la clase. En este caso se indica que se va a implementar el interface BodyTag. (el uso de final no es un requerimiento, pero hace a la clase más eficiente y rápida):

public final class p161_DbTag implements BodyTag
{

Declaramos las variables a nivel clase requeridas:

  private PageContext  pc   = null;
  private BodyContent  body = null;
  private StringBuffer sb   = new StringBuffer();
  private Connection   con  = null;
  private Statement    stmt = null;
  private ResultSet    rs   = null;

Implementamos el método setPageContext(). Lo único que se hace aquí es guardar la referencia al objeto PageContext:

  public void setPageContext( PageContext p )
  {
    pc = p;
  }

Puesto que no tenemos una etiqueta padre, dejamos al mínimo la implementación de los métodos setParent() y getParent():

  public void setParent( Tag t ) {}

  public Tag getParent() { return null; }

En el método doStartTag() haremos la conexión a tabla agenda de la base de datos db_curso. Se declara la trayectoria a la misma, la consulta a realizar, se crea el objeto Statement, y se ejecuta el mismo para obtener un ResultSet con los registros leidos.

A diferencia del ejemplo visto en la sección de variables scripting, en este ejemplo el método doStartTag invocará al método privado setVariables() y será en éste que se definirán las variables scripting.

Existen dos posibles valores a regresar de esta etiqueta: EVAL_BODY_BUFFERED y SKIP_BODY.

Nota:  A partir del API Java JSP 1.2, la constante EVAL_BODY_TAG ha sido desaprovada (deprecated). Se recomienda el uso de: BodyTag.EVAL_BODY_BUFFERED o IterationTag.EVAL_BODY_AGAIN.

La constante EVAL_BODY_BUFFERED pide la creación de un nuevo buffer, un objeto BodyContent para que el servidor de aplicaciones pueda evaluar el contenido del cuerpo de la etiqueta. Este es un valor ilegal a regresar del método doStartTag() si la clase no implementa el interface BodyTag.

La constante SKIP_BODY le indica al servidor de aplicacoines ignorar el contenido del cuerpo de la etiqueta.

  public int doStartTag() throws JspException
  {
    String path = "jdbc:mysql://localhost:3306/db_curso";
    String sql  = "Select Nom, Ape_pat From agenda";

    try
    {
      Class.forName( "com.mysql.jdbc.Driver" );

      con  = DriverManager.getConnection( path, "hera", "diosa" );
      stmt = con.createStatement();
      rs   = stmt.executeQuery( sql );

      setVariables();
    }
    catch( SQLException e )
    {
      throw new JspTagException( "Excepción SQL!" );
    }
    catch( ClassNotFoundException e )
    {
      throw new JspTagException( "Driver JDBC no encontrado." );
    }
    return EVAL_BODY_BUFFERED;
  }

Luego viene el método setBodyContent(), en el cual salvamos la referencia al objeto BodyContent en la variable body:

  public void setBodyContent( BodyContent b )
  {
    body = b;
  }

Puesto que no requerimos utilizar el método doInitBody(), implementamos sólo el mínimo del mismo:

  public void doInitBody() throws JspException {}

En el método setVariables() se van a definir y asignar las variables scripting Nom y Ape_pat del objeto PageContext.

En este método se va a ir haciendo el ciclo de lectura del objeto ResultSet: Se mueve el apuntador un registro, se obtienen los valores de los campos y se guardan en las variables a nivel página Nom y Ape_pat del objeto PageContext:

Nota:  Como puede apreciarse, el método lee un solo registro cada vez que es invocado.

  private boolean setVariables() throws JspTagException
  {
    try
    {
      if( rs.next() )
      {
        pc.setAttribute( "Nom",     rs.getObject( 1 ).toString() );
        pc.setAttribute( "Ape_pat", rs.getObject( 2 ).toString() );
        return true;
      }
      else
      {
        return false;
      }
    }
    catch( SQLException e )
    {
      throw new JspTagException( "Excepción SQL!" );
    }
  }

En el método doAfterBody() obtenemos el contenido del cuerpo de la etiqueta por medio del método getString() del objeto BodyContent (que fue asignado a la variable body), y concatenamos el objeto String obtenido al objeto StringBuffer. Una vez hecho esto, limpiamos el contenido del cuerpo.

Luego se invoca el método setVariables() nuevamente para obtener, en caso de que existan, los registros restantes del objeto ResultSet. Si quedan registros en este objeto, entonces se regresa la constante EVAL_BODY_AGAIN, la cual ocasiona la reevaluación del contenido del cuerpo de una etiqueta (con lo que el método doAfterBody() será invocado nuevamente).

Nota:  A partir del API Java JSP 1.2, la constante EVAL_BODY_TAG ha sido desaprovada (deprecated). Se recomienda el uso de: BodyTag.EVAL_BODY_BUFFERED o IterationTag.EVAL_BODY_AGAIN.

Por compatibilidad con JSP 1.1, se ha elegido que el valor de la constante IterationTag.EVAL_BODY_AGAIN sea igual al de la ahora desaprobada BodyTag.EVAL_BODY_TAG.

Mientras el método doAfterBody() siga regresando la constante EVAL_BODY_AGAIN, el método seguirá siendo invocado de forma repetida. En este caso las variables Nom y Ape_pat se asignan con los nuevos valores leidos de la base de datos.

Una vez que todos los registros han sido obtenidos (indicado por el valor false regresado por el método setVariables), continuamos y escribimos todos los registros (que se fueron concatenando al objeto StringBuffer) al navegador del cliente y regresamos la constante SKIP_BODY (la cual causa que termine el ciclo, con lo que el método doAfterBody() no se volverá a invocar) para terminar el proceso del contenido del cuerpo:

  public int doAfterBody() throws JspException
  {
    try
    {
      sb.append( body.getString() );
      body.clear();
    }
    catch( IOException e )
    {
      throw new JspTagException( "Excepción IO Fatal!" );
    }

    if( setVariables() )
    {
      return EVAL_BODY_AGAIN;
    }

    try
    {
      body.getEnclosingWriter().write( sb.toString() );
    }
    catch( IOException e )
    {
      throw new JspTagException( "Excepción IO Fatal!" );
    }
    return SKIP_BODY;
  }

En el método doEndTag() liberamos los objetos ResultSet, Statement y Connection:

  public int doEndTag() throws JspException
  {
    try
    {
      if( rs != null )
      {
        rs.close();
        rs = null;
      }
      if( stmt != null )
      {
        stmt.close();
        stmt = null;
      }
      if( con != null )
      {
        con.close();
        con = null;
      }
    }
    catch( SQLException e ) {}

    return EVAL_PAGE;
  }

Y finalmente en el método release() liberamos las referencias a los objetos PageContext, BodyContent y StringBuffer:

  public void release()
  {
    pc   = null;
    body = null;
    sb   = null;
  }

Para este ejercicio vamos a crear un nuevo paquete. Bajo dir_code/src/pkgs creamos el directorio pq05:


  D:\Dev
     |
     +-- curso_jsp
         |
         +-- src
             |
             +-- pkgs
                 :
                 |
                 +-- pq05

El código completo es el siguiente:

package pkgs.pq05;

import java.io.*;
import java.sql.*;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public final class p161_DbTag implements BodyTag
{
  private PageContext  pc   = null;
  private BodyContent  body = null;
  private StringBuffer sb   = new StringBuffer();
  private Connection   con  = null;
  private Statement    stmt = null;
  private ResultSet    rs   = null;

  public void setPageContext( PageContext p )
  {
    pc = p;
  }

  public void setParent( Tag t ) {}

  public Tag getParent() { return null; }

  public int doStartTag() throws JspException
  {
    String path = "jdbc:mysql://localhost:3306/db_curso";
    String sql  = "Select Nom, Ape_pat From agenda";

    try
    {
      Class.forName( "com.mysql.jdbc.Driver" );

      con  = DriverManager.getConnection( path, "hera", "diosa" );
      stmt = con.createStatement();
      rs   = stmt.executeQuery( sql );

      setVariables();
    }
    catch( SQLException e )
    {
      throw new JspTagException( "Excepción SQL!" );
    }
    catch( ClassNotFoundException e )
    {
      throw new JspTagException( "Driver JDBC no encontrado." );
    }
    return EVAL_BODY_BUFFERED;
  }

  public void setBodyContent( BodyContent b )
  {
    body = b;
  }

  public void doInitBody() throws JspException {}

  private boolean setVariables() throws JspTagException
  {
    try
    {
      if( rs.next() )
      {
        pc.setAttribute( "Nom",     rs.getObject( 1 ).toString() );
        pc.setAttribute( "Ape_pat", rs.getObject( 2 ).toString() );
        return true;
      }
      else
      {
        return false;
      }
    }
    catch( SQLException e )
    {
      throw new JspTagException( "Excepción SQL!" );
    }
  }

  public int doAfterBody() throws JspException
  {
    try
    {
      sb.append( body.getString() );
      body.clear();
    }
    catch( IOException e )
    {
      throw new JspTagException( "Excepción IO Fatal!" );
    }

    if( setVariables() )
    {
      return EVAL_BODY_AGAIN;
    }

    try
    {
      body.getEnclosingWriter().write( sb.toString() );
    }
    catch( IOException e )
    {
      throw new JspTagException( "Excepción IO Fatal!" );
    }
    return SKIP_BODY;
  }

  public int doEndTag() throws JspException
  {
    try
    {
      if( rs != null )
      {
        rs.close();
        rs = null;
      }
      if( stmt != null )
      {
        stmt.close();
        stmt = null;
      }
      if( con != null )
      {
        con.close();
        con = null;
      }
    }
    catch( SQLException e ) {}

    return EVAL_PAGE;
  }

  public void release()
  {
    pc   = null;
    body = null;
    sb   = null;
  }
}

• Archivo: p161_DbTag.java

Salvamos el código en dir_code/src/pkgs/pq05.

2. La Clase TagExtraInfo

Como vimos anteriormente, requerimos proveer una clase que extienda la clase TagExtraInfo si deseamos declarar variables scripting desde una etiqueta JSP.

Puesto que estamos declarando dos variables scripting, crearemos la clase p162_InfoAdic para extender a TagExtraInfo y sobreescribiremos el método getVariableInfo() para que regrese un vector de dos objetos VariableInfo para cada variable scripting a hacer disponible.

Las dos variables scripting regresadas (Nom y Ape_pat) son de tipo String, se requiere (true) que sean declaradas, y su alcance es dentro de las etiquetas de apertura y cierre (NESTED):

package pkgs.pq05;

import javax.servlet.jsp.tagext.*;

public class p162_InfoAdic extends TagExtraInfo
{
  public VariableInfo[] getVariableInfo( TagData data )
  {
    return new VariableInfo[]
    {
      new VariableInfo( "Nom",     "java.lang.String", true,
        VariableInfo.NESTED ),

      new VariableInfo( "Ape_pat", "java.lang.String", true,
        VariableInfo.NESTED )
    };
  }
}

• Archivo: p162_InfoAdic.java

Salvamos el código en dir_code/src/pkgs/pq05.

3. El Descriptor de la Biblioteca de Etiquetas

Actualizaremos ahora nuestro archivo descriptor de biblioteca de etiquetas (MisTags.tld). Abrimos el archivo dir_code/web/WEB-INF/tlds/MisTags.tld y agregamos la definición de la nueva etiqueta:

<?xml version="1.0" encoding="utf-8" ?>

<!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>MisTags</short-name>
  <uri></uri>
  <description>Mi Biblioteca de Etiquetas</description>

  <tag>
    <name>mitag1</name>
    <tag-class>pkgs.pq03.p121_TagSimple</tag-class>
    <body-content>empty</body-content>
    <description>Mi primer etiqueta JSP</description>

    <attribute>
      <name>nombre</name>
      <required>false</required>
    </attribute>
  </tag>

  <tag>
    <name>mitag2</name>
    <tag-class>pkgs.pq03.p131_ScripVars</tag-class>
    <tei-class>pkgs.pq03.p132_InfoAdic</tei-class>
    <body-content>JSP</body-content>
    <description>Mi segunda etiqueta JSP</description>
  </tag>

  <tag>
    <name>mitag3</name>
    <tag-class>pkgs.pq05.p161_DbTag</tag-class>
    <tei-class>pkgs.pq05.p162_InfoAdic</tei-class>
    <body-content>JSP</body-content>
    <description>Mi tercer etiqueta JSP</description>
  </tag>
</taglib>

• Archivo: MisTags.tld

Salvamos los cambios y ya estamos listos para crear la página JSP en la que probaremos nuestras nuevas clases.

4. La Página JSP

La parte fundamental de la página va a ser la siguiente, en la que con la directiva taglib llamamos a nuestra bibioteca de etiquetas JSP para acceder nuestra etiqueta de acceso a la base de datos y obtener los datos de cada registro:

<%@ taglib uri="WEB-INF/tlds/MisTags.tld" prefix="xgms" %>
<xgms:mitag3>
  <tr>
    <td> <%= Nom %>     </td>
    <td> <%= Ape_pat %> </td>
  </tr>
</xgms:mitag3>

Como puede apreciarse, es en el contenido del cuerpo de la etiqueta que se presenta el valor de las variables scripting.

El código completo de la página JSP es el siguiente:

<html>

<head>
<title>
Etiqueta Personalizada de Acceso a MySQL
</title>
</head>

<body bgcolor="#ffeada">

<table border="0" align="center" width="90%" cellspacing="2" cellpadding="2">
  <tr>
    <td> Nombre   </td>
    <td> Apellido </td>
  </tr>
<%@ taglib uri="WEB-INF/tlds/MisTags.tld" prefix="xgms" %>
<xgms:mitag3>
  <tr>
    <td> <%= Nom %>     </td>
    <td> <%= Ape_pat %> </td>
  </tr>
</xgms:mitag3>
</table>

</body>
</html>

• Archivo: p163_DbTag.jsp

Salvamos la página en dir_code/web, ejecutamos el Ant y la accesamos con el URL: http://localhost/curso/p163_DbTag.jsp.

5. Pasando a Modo de Producción

Como se vio durante el proceso de configuración del Tomcat (2.d, 2.e), algunos de los cambios que se hicieron fueron para simplificar el proceso de desarrollo. Sin embargo, para un ambiente de producción tales configuraciones no resultan adecuadas, ya sea por cuestión de eficiencia, portabilidad o seguridad.

Entre los cambios que deben realizarse para probar que nuestra aplicación se despliegue correctamente en un ambiente de producción, están:

Nota:  En el CD se incluyen los archivos de configuración con estas modificaciones (ver más adelante).

Finalmente, otro detalle a considerar es que la aplicación debe incluir todos los recursos que utilice (imágenes, drivers, etc).

En nuestro caso hemos hecho uso de un driver JDBC para acceder nuestra base de datos. Como podrá recordarse, éste fue instalado en dir_java2/jre/lib/ext, lo cual permitió que fuera accesible durante la compilación y ejecución de la aplicación. Para que ahora quede incluido en la aplicación, debe copiarse el archivo dir_java2/jre/lib/ext/mysql-connector-java-3.0.6-stable-bin.jar al directorio dir_code/web/WEB-INF/lib.